Pokazywanie postów oznaczonych etykietą wicket. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą wicket. Pokaż wszystkie posty

21 stycznia 2009

Reklama nie zawadzi, jeśli odpowiednio umotywowana (przekonajmy się)

5 komentarzy
Ja tu dopiero, co wspomniałem o potrzebie rozpoznania uruchamiania testów Groovy z pomocą Apache Maven (Relacja z rozdziału 3. "More Advanced Groovy" z "Beginning Groovy and Grails"), a do mojej skrzynki trafia wiadomość o zmianach na stronie Grails > Maven Integration. Zbieg okoliczności, czy ktoś dba, abym nie stracił zainteresowania Groovy & Grails?! Na razie temat czeka cierpliwie na swoją kolej (która z pewnością nadejdzie - nie dziś, nie jutro, ale na pewno wkrótce).

Dla zainteresowanych przyspieszeniem rozpoznawania Grails natrafiłem na ciekawą prezentację Guillaume Laforge z Devoxx 2008, która trwała...3 godziny (!) Gość ma klasę, jeśli nie tylko potrafi programować, ale również przedstawiać temat w zrozumiały sposób. Dla niezorientowanych wspomnę, że jest on głównodowodzącym projektem Grails. Nie wiem, jak mu poszło, bo nagrania z jego wystąpienia nie ma, ale za to prezentacja jest do publicznej konsumpcji - Groovy and Grails in Action - Devoxx 2008 - University - Guillaume Laforge. Warto, bo nie zabiera wiele czasu (mimo liczby slajdów), a wiedza z samego źródła.

Dzisiaj miałem dwie ciekawostki, które sprawiły, że moje prywatne zainteresowania mogą mieć znaczący użytek służbowy. Podczas dzisiejszej rozmowy na temat IBM WebSphere Business Services Fabric (WBSF) V6.2 z moim podopiecznym, w ramach programu praktyk studenckich w IBM, moją uwagę przykuł jeden URL w aplikacji zarządzającej WBSF - ten, który zawierał w adresie...wicket:bookmarkablePage (!) Tylko mignęło, ale to wystarczyło, abym innym okiem spojrzał na WBSF, chyba nawet trochę przychylniejszym (chociaż i tak było wystarczająco przychylne). Wciąż jeszcze nieznane jest dla mnie biznesowe zastosowanie WBSF, ale sam fakt, że wykorzystuje pod spodem Apache Wicket od razu sprawił, że nie mogę doczekać się wyników mojego studenta, który ma za zadanie przedstawić produkt ze szczegółami. Możliwość połączenia zainteresowań prywatnych z obowiązkami służbowymi sprawia, że to, co człowiek robi w wolnej chwili staje się natychmiast zajęciem służbowym, a to cieszy każdego zawalonego robotą, czyż nie? Oczywiście ma to swoje minusy. Kiedy zainteresowanie staje się obowiązkiem, niewiele dzieli go od stania się ciężarem i znużenia nim. Sądzę, że do znużenia Wicketem nie dojdzie u mnie prędko (aczkolwiek dawno o nim nie wspominałem), a wraz z Groovy czuję, że wiele dobrego mnie czeka. Jeśli mógłbym "pożenić" te technologie w rozwiązaniach służbowych, tym lepiej dla mnie i klientów rozwiązań. Pozostaje tylko wierzyć, że będzie to dla nich równie wartościowym połączeniem, jak dla mnie.

Drugą ciekawostką, o której już wiedziałem od jakiegoś czasu, ale teraz nabrała innego wydźwięku jest IBM WebSphere sMash. Komercyjny aczkolwiek bezpłatny produkt firmy IBM, który oparty jest o otwarty projekt Project Zero. A dlaczego o nim, kiedy powinienem właśnie przedstawić kolejny rozdział książki Beginning Groovy and Grails: From Novice to Professional? sMash jest szkieletem webowym, w którym aplikacje pisze się w PHP oraz...Groovy. Tu wszyscy wstają, klaszczą i rozpoczynamy imprezę! ;-) To jest właśnie ten moment, w którym jednym zamachem, rozpoznając Groovy, można jednocześnie rozpracować kolejny - IBM WebSphere sMash. Cudownie!

Można sobie teraz tylko wyobrazić miny wszystkich tych, którzy byli przeciwni stosowaniu obu technologii jako niedojrzałych lub (co bardziej kuriozalne) otwartych. Zdaje się, że już przynajmniej jeden gigant IT - IBM - postawił na Apache Wicket i Groovy. Czy trzeba więcej argumentów, aby przekonać najbardziej nieprzekonanych kierowników IT, architektów, szefów grup rozwojowych, że komercyjne produkty z płatnym wsparciem oparte są o produkty darmowe, co sprawia, że dojrzałość tych drugich jest wystarczająca do zastosowań komercyjnych? Sądzę, że tym samym odparty został ostatni zarzut o ryzyku wdrożenia obu i jedynym problemem może być wyłącznie nasz brak zainteresowania ich wykorzystaniem. Jeśli nawet miałem wątpliwości wczoraj w kontekście wartości płynących w promowaniu Groovy czy Wicket, bądź jeszcze dzisiaj rano, teraz już całkowicie się ich pozbyłem. Ktoś jeszcze tego doświadczył?

p.s. Nie byłbym sobą, gdybym nie wspomniał, że ten blog został zgłoszony do konkursu Blog Roku 2008. Coś nie może się ruszyć w rankingu przesłanych SMSów i bidulek siedzi na drugiej stronie w kolejności oddanych głosów. Proponuję natychmiast wysłać SMSa o treści B00204 (czytaj: be-zero-zero-dwa-zero-cztery) pod numer 7144 za 1,22PLN brutto i mieć to z głowy. Po co czekać na ostatni dzień?! ;-)

09 stycznia 2009

Wtyczka Grails do Wicketa do...kosza

2 komentarzy
Liczyłem na to, że popróbuję się z wtyczką Wicket dla Grails, ale się przeliczyłem. To znaczy, popróbowałem się z nią, aż doszedłem do kroku instalacji i...nic.

Dotychczasowa praca z Grails pozwala mi sądzić, że tworzenie aplikacji w Grails to nic innego jak programowanie w Groovy ze wsparciem bibliotek pomocniczych, jak wspomniany Wicket, dzięki pomocy wtyczek. Listę dostępnych wtyczek można sprawdzić poleceniem grails list-plugins.
  jacek@dev ~
$ grails list-plugins
Welcome to Grails 1.1-beta2 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/dev/grails

Base Directory: C:\dev\cygwin\home\jacek
Running script c:\dev\grails\scripts\ListPlugins_.groovy
Environment set to development
...i tutaj cała lista wtyczek
Plug-ins you currently have installed are listed below:
-------------------------------------------------------------

You do not have any plugins installed.

To find more info about plugin type 'grails plugin-info [NAME]'

To install type 'grails install-plugin [NAME] [VERSION]'

For further info visit http://grails.org/Plugins
Wtyczki instaluje się w projekcie grailsowym i są one z nim związane. Wykonanie większości poleceń grails daje inne rezultaty w zależności od projektu. Zgodnie z komunikatami z list-plugins więcej informacji o danej wtyczce można znaleźć wykonując grails plugin-info.
  jacek@dev ~
$ grails plugin-info wicket
Welcome to Grails 1.1-beta2 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/dev/grails

Base Directory: C:\dev\cygwin\home\jacek
Running script c:\dev\grails\scripts\PluginInfo_.groovy
Environment set to development
Reading remote plugin list ...

--------------------------------------------------------------------------
Information about Grails plugin
--------------------------------------------------------------------------
Name: wicket | Latest release: 0.6
--------------------------------------------------------------------------
Provides integration between Grails and the Wicket framework
--------------------------------------------------------------------------
Author: Graeme Rocher
--------------------------------------------------------------------------
Find more info here: http://grails.org/Wicket+Plugin
--------------------------------------------------------------------------

A plug-in that makes Wicket (http://wicket.sourceforge.net/)
the default view rendering framework for Grails.
Wicket is a component oriented framework that, like Grails,
embraces convention-over-configuration approaches.

--------------------------------------------------------------------------
Available full releases: 0.2 0.3 0.4 0.5 0.6
Available zip releases: 0.1

To get info about specific release of plugin 'grails plugin-info [NAME] [VERSION]'

To get list of all plugins type 'grails list-plugins'

To install latest version of plugin type 'grails install-plugin [NAME]'

To install specific version of plugin type 'grails install-plugin [NAME] [VERSION]'

For further info visit http://grails.org/Plugins
Wystarczy, więc zapoznać się z dokumentacją i zdecydować o instalacji wtyczki poleceniem grails install-plugin.
  jacek@dev ~
$ grails install-plugin wicket
Welcome to Grails 1.1-beta2 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/dev/grails

Base Directory: C:\dev\cygwin\home\jacek
C:\dev\cygwin\home\jacek does not appear to be part of a Grails application.
The following commands are supported outside of a project:
create-app
create-plugin
help
list-plugins
package-plugin
plugin-info
set-proxy
Run 'grails help' for a complete list of available scripts.
Czyż nie wspomniałem o tym, że nie mogę wykonać polecenia instalacji wtyczki poza projektem?! Właśnie, a jak stworzyć projekt? Kolejnym poleceniem Grails - grails create-app.
  jacek@dev /cygdrive/c/projs
$ grails create-app
Welcome to Grails 1.1-beta2 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/dev/grails

Base Directory: C:\projs
Running script c:\dev\grails\scripts\CreateApp_.groovy
Environment set to development
Application name not specified. Please enter:
WicketGrailsDemo
[mkdir] Created dir: C:\projs\WicketGrailsDemo\src
...
Created Grails Application at C:\projs/WicketGrailsDemo
Teraz instalacja wtyczki kończy się pomyślnie.
  jacek@dev /cygdrive/c/projs/WicketGrailsDemo
$ grails install-plugin wicket
Welcome to Grails 1.1-beta2 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/dev/grails

Base Directory: C:\projs\WicketGrailsDemo
Running script c:\dev\grails\scripts\InstallPlugin.groovy
Environment set to development
Reading remote plugin list ...
[copy] Copying 1 file to C:\Documents and Settings\Administrator\.grails\1.1-beta2\projects\WicketGrailsDemo\plugins
Installing plug-in wicket-0.6
[mkdir] Created dir: C:\Documents and Settings\Administrator\.grails\1.1-beta2\projects\WicketGrailsDemo\plugins\wicket-0.6
[unzip] Expanding: C:\Documents and Settings\Administrator\.grails\1.1-beta2\plugins\grails-wicket-0.6.zip
into C:\Documents and Settings\Administrator\.grails\1.1-beta2\projects\WicketGrailsDemo\
plugins\wicket-0.6
Executing wicket-0.6 plugin post-install script ...
Warning, target causing name overwriting of name default
[mkdir] Created dir: c:\projs\WicketGrailsDemo\grails-app\pages
: Warning: Could not find file c:\projs\WicketGrailsDemo\null\src\templates\WicketApplication.groovy to copy.
at org.apache.tools.ant.taskdefs.Copy.execute(Copy.java:420)
at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:105)
at org.apache.tools.ant.Task.perform(Task.java:348)
at Script1.run(Script1.groovy:15)
at _PluginDependencies_groovy$_run_closure18.doCall(_PluginDependencies_groovy:416)
at _PluginDependencies_groovy$_run_closure25.doCall(_PluginDependencies_groovy:661)
at _GrailsPlugins_groovy$_run_closure2.doCall(_GrailsPlugins_groovy:78)
at gant.Gant$_dispatch_closure4.doCall(Gant.groovy:306)
at gant.Gant.dispatch(Gant.groovy:316)
at gant.Gant.this$2$dispatch(Gant.groovy)
at gant.Gant.invokeMethod(Gant.groovy)
at gant.Gant.processTargets(Gant.groovy:461)
at gant.Gant.processTargets(Gant.groovy:446)
Error installing plugin: Warning: Could not find file
c:\projs\WicketGrailsDemo\null\src\templates\WicketApplication.groovy
to copy.: Warning: Could not find file
c:\projs\WicketGrailsDemo\null\src\templates\WicketApplication.groovy to copy.
Taa, chciałbym. Niepokojący jest ten null w ścieżce. I dlaczego projekty i wtyczki instalowane są w katalogu domowym użytkownika?! Przeglądając wicket-0.6/scripts/_Install.groovy odniosłem wrażenie, że z jakiś nieznanych mi powodów zmienna "pluginHome" nie jest poprawnie ustawiona, więc...zarzuciłem temat. Za słaby jestem jeszcze z Grails, aby próbować się z tego typu problemami. Zdaje się, że ostatnie zmiany w kodach wtyczki to początek 2008 - 06 March 2008, więc można przypuszczać, że Wicket i Grails to nie jest dobry pomysł na początek poznawania Grails. Zabieram się za inną - cas-client - This plugin provides client integration for JA-SIG CAS. Mam do rozpoznania wykorzystanie CAS w aplikacjach javowych, więc może się uda w połączeniu z Grails.

21 sierpnia 2008

"Wicket in Action" z Manning dostępny publicznie

1 komentarzy
W końcu światło dzienne ujrzała książka o Apache Wicket - Wicket in Action autorstwa jego twórców Martijn Dashorst oraz Eelco Hillenius. Wszyscy poszukujący "biblii" dla Wicketa w końcu ją mają. Członkowie Warszawa JUG również - zajrzyj do Biblioteki Warszawskiego JUGa. Zapraszam do lektury i recenzji (za co przysługują nam kolejne pozycje od wydawnictwa). Czytałem wersję MEAP (Manning Early Access Program), ale przynajmniej wizualnie książka się zmieniła, więc przyjdzie mi zapewne czytać ją ponownie.

W międzyczasie padła propozycja prezentacji o Wicket w ramach łódzkiego JUGa 20. września, więc zainteresowanych zapraszam na przedyskutowanie tematu na żywo z praktykiem - Piotrem Przybylakiem. Do Łodzi w sumie niedaleko ;-)

Final Ebook Delivery *Wicket in Action*
Dear MEAP Subscriber,

Manning is pleased to announce the release of the PDF Ebook version of Wicket in Action. As part of your subscription to the Early Access version of this title, you also can download the finished PDF Ebook Edition at no extra charge.

=======================================
How to Download the PDF Ebook Edition
=======================================

Please download your early PDF Ebook from the following URL(s):

* Wicket in Action

Please be aware that your links are valid for 5 days. If your link expires, please contact Manning customer support so that we can renew your download links.

Thanks again!

Manning Publications Co.
www.manning.com

29 maja 2008

java.io.File.toURI().toURL() oraz słów kilka o Ajaxie w Wicket

1 komentarzy
Przeglądając ostatnie zmiany w Apache Geronimo natrafiłem na zmianę związaną z java.io.File.toURL(), którą Jason zmigrował do File.toURI().toURL(). Kilkakrotnie już trafiałem na taką konwersję i nigdy nie wiedziałem, jaki jest dokładnie powód. Dzisiaj postanowiłem sprawdzić javadoc dla tych metod i okazało się, że:
  • Java 6 "przyłożyła" adnotację @Deprecated do metody File.toURL() z komentarzem, że metoda nie dbała o poprawość zwracanego URL, który mógł zawierać niedozwolone znaki, np. #, &, {, + czy ?.
  • Jako poprawne wywołanie tej metody wskazano na właśnie File.toURI().toURL()
Dobrze wiedzieć te ciekawostki, aby nie popaść w kłopoty podczas wdrożenia aplikacji, czy podczas egzaminu SCJP 6. Od tej pory nie korzystamy z File.toURL. Nigdy!

W ten sposób wykonanie poniższego kawałka kodu:
 String sciezka = "że#to&nie{powinno+działać?";
File pathFile = new File(sciezka);
System.out.println("1. " + pathFile.toURL());
System.out.println("2. " + pathFile.toURI().toURL());
zwróci:
 1. file:/C:/sandbox/że#to&nie{powinno+działać?
2. file:/C:/sandbox/że%23to&nie%7Bpowinno+działać%3F
Jak można zauważyć pierwszy z URLi jest niepoprawny i próba otwarcia takiego adresu spowoduje wyjątek. Dodatkowo dokumentacja java.net.URL również wskazuje na klasę java.net.URI, właśnie ze względu na brak dbałości o poprawność zwracanych adresów URL. Więcej informacji o niedozwolonych znakach w adresie (ze względu na szczególne ich znaczenie) można znaleźć w RFC 2396: Uniform Resource Identifiers (URI): Generic Syntax w sekcji 2.4.3. Excluded US-ASCII Characters.

W trakcie lektury książki Wicket in Action natrafiłem na wzmianki o wsparciu Ajaxa. Dla ustalenia uwagi moją znajomość JavaScript i innych technologii klienckich określiłbym bliską zeru, więc każdorazowe wsparcie ze strony szkieletów aplikacyjnych w tym zakresie witanych jest przeze mnie z wielkim entuzjazmem. Tak jest z JavaServer Faces, i tak jest z Apache Wicket. Oba rozwiązania mają swoje zalety, gdzie podstawową zaletą JSF podczas porównania z Wicket jest możliwość uruchamiania JSP i jego znaczników, gdzie właśnie to jest podnoszone jako zaleta Wicketa, w którym użycie JSP jest niezwykle utrudnone, jeśli w ogóle w pełni możliwe. Zwróciłem na to uwagę podczas wykorzystania znaczników jpivot dla kostek OLAP, gdzie musiałem zwizualizować kilka takich struktur i jedynym sposobem mogło być wykorzystanie JSF.

Wracając do Wicketa, bo o nim chciałem wspomnieć i jego wsparciu Ajaxa, uruchomienie wstawek ajaksowych sprowadza się do wykorzystania odpowiedników ajaksowanych dla kontrolek akcyjnych - łącze (ang. link) czy przycisk (ang. button), np. org.apache.wicket.ajax.markup.html.AjaxFallbackLink dla org.apache.wicket.markup.html.link.Link (de facto AjaxFallbackLink rozszerza Link) oraz org.apache.wicket.ajax.markup.html.form.AjaxFallbackButton dla org.apache.wicket.markup.html.form.Button. Zaletą stosowania typów AjaxFallback* jest jednoczesna obsługa żądań ajaksowych i nieajaksowych, zlecając Wicketowi decyzję jaką obsługę wybrać do możliwości klienta (przeglądarki).

Dla zniecierpliwionych, podaję 5-minutowy "przepis" na ajaksową aplikację z Wicket. Jedyne co potrzeba, to Apache Maven 2 (dalej m2) oraz Eclipse IDE z wtyczką M2Eclipse, do pracy z projektami kontrolowanymi przez m2.

1. Utworzenie projektu wicket-ajax-demo
 $ mvn archetype:create \
-DarchetypeGroupId=org.apache.wicket \
-DarchetypeArtifactId=wicket-archetype-quickstart \
-DarchetypeVersion=1.4-m1 \
-DgroupId=pl.jaceklaskowski.wicket \
-DartifactId=wicket-ajax-demo
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO] task-segment: [archetype:create] (aggregator-style)
[INFO] ------------------------------------------------------------------------
...
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating OldArchetype: wicket-archetype-quickstart:1.4-m1
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: pl.jaceklaskowski.wicket
[INFO] Parameter: packageName, Value: pl.jaceklaskowski.wicket
[INFO] Parameter: basedir, Value: c:\projs\sandbox
[INFO] Parameter: package, Value: pl.jaceklaskowski.wicket
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: artifactId, Value: wicket-ajax-demo
[INFO] ********************* End of debug info from resources from generated POM ***********************
[INFO] OldArchetype created in dir: c:\projs\sandbox\wicket-ajax-demo
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
2. Import projektu do Eclipse

Importuję projekt wicket-ajax-demo do Eclipse z pomocą File > Import > Maven Projects.


3. Podniesienie wersji Wicket do 1.4-m1 w pom.xml

Poprawiam pom.xml o podniesienie wersji Wicket do 1.4-m1.
 <wicket.version>1.4-m1</wicket.version>
Eclipse zatroszczy się pobraniem wymaganych zależności projektowych. Dobrze być wtedy w Sieci, aby mogły być pobrane. Zaleca się skorzystanie z menu Maven > Download Sources, aby Eclipse pobrał źródła zależności, w tym i Wicketa z Sieci, co umożliwi podejrzenie jak to działa bezpośrednio w kodzie.

Podniesienie wersji spowoduje, że Eclipse oznaczy kilka klas jako błędne, co będzie wymagało kolejnego uaktualnienia pom.xml o wersję kompilatora do 1.5 za pomocą konfiguracji wtyczki maven-compiler-plugin.
 <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
Po zmianie najlepiej należy ponownie zaimportować projekt (wcześniej go usuwając) bądź urchomić polecenie mvn eclipse:eclipse z linii poleceń w katalogu projektu i F5 (Refresh) w Eclipse.

4. Modyfikacja strony domowej aplikacji - HomePage.html
 <html>
<head>
<title>Wicket Quickstart Archetype Homepage</title>
</head>
<body>
<strong>Wicket Quickstart Archetype Homepage</strong>
<br/><br/>
<span wicket:id="message">message will be here</span>
<br>
<a href="#" wicket:id="link">Wciśnij mnie</a>, a napis wyżej się zmieni.
</body>
</html>
5. Modyfikacja strony domowej aplikacji - pl.jaceklaskowski.wicket.HomePage

Dodanie wicketowego elementu do strony HTML wymaga odpowiedniej zmiany w klasie strony.
 package pl.jaceklaskowski.wicket;

import java.util.Calendar;
import java.util.Locale;

import org.apache.wicket.PageParameters;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.util.convert.IConverter;

public class HomePage extends WebPage {

private static final long serialVersionUID = 1L;

private Label label;
private Link link;

public HomePage(final PageParameters parameters) {

label = new Label("message", "If you see this message wicket is properly configured and running");
label.setOutputMarkupId(true);
add(label);
add(new AjaxFallbackLink("link") {

@Override
public void onClick(AjaxRequestTarget target) {
label.getModel().setObject("Wcisnieto mnie o " + Calendar.getInstance(new Locale("pl")).getTime());
if (target != null) {
target.addComponent(label);
}
}

});
}
}
Sprawdzenie target != null jest konieczne dla sytuacji, w której żadanie było nieajaksowe (klient nie wspiera Ajaxa).

Konieczne jest również wywołanie metody label.setOutputMarkupId(true), której brak spowoduje:
 java.lang.IllegalArgumentException: cannot update component that does not have setOutputMarkupId property set to true. 
Component: [Component id = message, page = pl.jaceklaskowski.wicket.HomePage, path = 1:message.Label, isVisible = true, isVersioned = true]
at org.apache.wicket.ajax.AjaxRequestTarget.addComponent(AjaxRequestTarget.java:343)
at pl.jaceklaskowski.wicket.HomePage$1.onClick(HomePage.java:31)
at org.apache.wicket.ajax.markup.html.AjaxFallbackLink$1.onEvent(AjaxFallbackLink.java:73)
at org.apache.wicket.ajax.AjaxEventBehavior.respond(AjaxEventBehavior.java:161)
at org.apache.wicket.ajax.AbstractDefaultAjaxBehavior.onRequest(AbstractDefaultAjaxBehavior.java:298)
at org.apache.wicket.request.target.component.listener.BehaviorRequestTarget.processEvents(BehaviorRequestTarget.java:100)
at org.apache.wicket.request.AbstractRequestCycleProcessor.processEvents(AbstractRequestCycleProcessor.java:91)
at org.apache.wicket.RequestCycle.processEventsAndRespond(RequestCycle.java:1174)
at org.apache.wicket.RequestCycle.step(RequestCycle.java:1251)
at org.apache.wicket.RequestCycle.steps(RequestCycle.java:1352)
at org.apache.wicket.RequestCycle.request(RequestCycle.java:499)
at org.apache.wicket.protocol.http.WicketFilter.doGet(WicketFilter.java:375)
at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:199)
...
podczas uruchomienia aplikacji bez tego wywołania.

6. Uruchomienie aplikacji z mvn jetty:run

Ostatecznie należy sprawdzić poprawność wprowadzonych zmian uruchamiając aplikację z wybranym kontenerem servletów. Jako, że korzystam z m2 stawiam na Jetty, którego uruchomienie i uruchomienie aplikacji webowej sprowadza się do wykonania polecenia mvn clean jetty:run (dodałem clean dla zapewnienia, że klasy zostały skompilowane w odpowiedniej wersji javy).
 $ mvn clean jetty:run
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'jetty'.
[INFO] ------------------------------------------------------------------------
[INFO] Building quickstart
[INFO] task-segment: [clean, jetty:run]
[INFO] ------------------------------------------------------------------------
...
[INFO] [jetty:run]
[INFO] Configuring Jetty for project: quickstart
[INFO] Webapp source directory = C:\projs\sandbox\wicket-ajax-demo\src\main\webapp
[INFO] web.xml file = C:\projs\sandbox\wicket-ajax-demo\src\main\webapp\WEB-INF\web.xml
[INFO] Classes = C:\projs\sandbox\wicket-ajax-demo\target\classes
2008-05-29 23:09:58.908::INFO: Logging to STDERR via org.mortbay.log.StdErrLog
[INFO] Context path = /wicket-ajax-demo
[INFO] Tmp directory = determined at runtime
[INFO] Web defaults = org/mortbay/jetty/webapp/webdefault.xml
[INFO] Web overrides = none
[INFO] Webapp directory = C:\projs\sandbox\wicket-ajax-demo\src\main\webapp
[INFO] Starting jetty 6.1.9 ...
2008-05-29 23:09:58.987::INFO: jetty-6.1.9
2008-05-29 23:09:59.112::INFO: No Transaction manager found - if your webapp requires one, please configure one.
INFO - Application - [WicketApplication] init: Wicket core library initializer
...
INFO - WebApplication - [WicketApplication] Started Wicket version 1.4-m1 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. ***
********************************************************************
2008-05-29 23:09:59.924::INFO: Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
Przechodzę na adres http://localhost:8080/wicket-ajax-demo.

Wciśnięcie łącza Wciśnij mnie spowoduje zmianę napisu na zawierający datę wykonania obsługi wciśnięcia.

Niewielka aplikacja, a jak cieszy. I ta prostota Wicketa w kontekście obsługi Ajaxa - zero JavaScripu czy podobnie (!)

Pytanie konkursowe: Jaka jest rola typów AjaxFallback{Link,Button} w Wicket?

Do zobaczenia na JAVArsovii w nadchodzącą sobotę 31.05.2008 w godzinach 9:00-19:00, gdzie podczas mojej prezentacji o Apache Wicket pokaże tą i inne aplikacje webowe z nim na pokładzie. Jutro audycja w Polskim Radio EURO o 9:00. Ciekawym Waszych opinii odnośnie naszego radiowego występu (więcej o nim we wczorajszej notatce 3 dni do JAVArsovii 2008, audycja w Polskim Radiu, @Override i skróty netbeansowe). Wracam do lektury książki Wicket in Action.

11 maja 2008

31. spotkanie Warszawskiej Grupy Użytkowników Technologii Java (Warszawa JUG)

0 komentarzy
Warszawska Grupa Użytkowników Technologii Java (Warszawa JUG) zaprasza na 31. spotkanie, które odbędzie się 13.05.2008 o godzinie 18:00 w sali 5440 Wydziału MIMUW przy ul. Banacha 2 w Warszawie.

Temat prezentacji: Apache Wicket w przykładach
Prowadzący: Jacek Laskowski

Apache Wicket jest szkieletem webowym, którego podwalinami jest zarzucenie JSP jako technologii prezentacji, stworzenie warstwy modelu opartego o POJO i zniesienie konieczności stosowania wszechobecnego XMLa. Na spotkaniu Jacek zaprezentuje wiele przykładów, za pomocą których zamierza zademonstrować siłę Wicketa i przekonać do jego spróbowania w projektach. Odświeżające odseparowanie HTMLa od JSP, całkowite zaniechanie XMLa do konfiguracji aplikacji oraz wiele ciekawych rozwiązań wspierających tworzenia aplikacji webowych jak silne typowanie obiektów o zasięgu sesji czy aplikacji, wstrzymywanie żądania i przekierowywanie na stronę uwierzytelnienia są ciekawą alternatywą dla innych, znanych szkieletów webowych. Wiele z rozwiązań Wicket z pewnością sprowokuje do poszukiwania ich odpowiedników w obecnie korzystanych szkieletach. Mnóstwo przykładów gwarantuje praktyczne zrozumienie Wicketa.

Prezentację poprowadzi Jacek Laskowski, który jest pasjonatem tworzenia aplikacji javowych korzystających z Java EE i projektów otwartych. Jacek jest członkiem zespołów Apache Geronimo, Apache OpenEJB, Apache ServiceMix, Apache XBean oraz Apache ActiveMQ. Jestem założycielem i liderem Warszawskiej Grupy Użytkowników Technologii Java (Warszawa JUG). Bierze aktywny udział w wielu forach dyskusyjnych i otwartych projektach. Własne doświadczenia z językiem Java, Java EE i otwartymi projektami opisuje w Notatniku Projektanta Java EE. Służbowo pracuje na stanowisku konsultanta oprogramowania w IBM Polska.

Planowany czas prezentacji to 1,5 godziny, po której planuje się 15-30-minutową dyskusję.

Wstęp wolny!

Zapraszam w swoim imieniu i grupy Warszawa JUG!

09 maja 2008

Umiędzynarodowienie w Apache Wicket

3 komentarzy
Tempo rozwoju Apache Wicket może zdumiewać, gdyż jeszcze nie tak dawno podnosiłem wersję Wicket mojej testowej aplikacji do 1.3.3, a już mamy 1.4-m1. Z pomocą Apache Maven 2 temat sprowadził się do uaktualnienia pom.xml, w którym podniosłem wersję Wicketa oraz Spring Framework (do wersji 2.5.4). Jedną z głównych zmian w Wicketa 1.4 jest skorzystanie z mechanizmu wzorców w Javie (Java Generics), tak że musiałem zmienić sygnaturę metody WicketDemoApplication.getHomePage(), która zawęziła typ zwracanych obiektów do Class<? extends org.apache.wicket.Page>.
 c:\projs\sandbox\wicket-demo\src\main\java\pl\jaceklaskowski\wicket\WicketDemoApplication.java:[66,20] getHomePage() 
in pl.jaceklaskowski.wicket.WicketDemoApplication cannot override getHomePage() in org.apache.wicket.Application;
attempting to use incompatible return type
found : java.lang.Class<?>
required: java.lang.Class<? extends org.apache.wicket.Page>
Do mojej prezentacji Wicketa na kolejnym,wtorkowym spotkaniu Warszawa JUG pozostało już niewiele dni, więc kończąc lekturę książki Pro Wicket (Apress) trafiłem na rozdział przedstawiający mechanizm i18n - umiędzynarodowienia, który polega na przeniesieniu napisów do plików properties z przypisaniem im identyfikatorów wraz z ich wykorzystaniem w dedykowanych znacznikach <wicket:message>. Identyfikatory są niezmienne i zapisane są w ciele stron aplikacji, podczas gdy ich wartości zależne od języka są zapisane w plikach properties. Wskazanie miejsca umiędzynarodowienia polega na skorzystaniu ze znacznika <wicket:message key="..." />, który odszuka właściwego napisu dla podanego klucza (identyfikatora) wskazanego przez atrybut key. Pozostaje przedstawić, gdzie pliki properties powinny się znajdować. Jest kilka wariantów umieszczenia pliku properties, co daje nam różne poziomy granulacji komunikatów dla pojedyńczego formularza, pojedyńczej strony, dla całej grupy stron czy dla całej aplikacji. Nie wnikając w niepotrzebne szczegóły, umieszczę napisy w pliku odpowiadającym nazwie strony. Cała radość w "czystość" plików HTML została nieznacznie zaburzona wprowadzeniem specyficznych dla Wicketa znaczników <wicket:message>.

Oto strona DaneOsobowe.html ze znacznikami <wicket:message/>:
 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:wicket="http://wicket.apache.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Wicket Demo App</title>
</head>
<body>
<strong>Wicket Demo App</strong>
<span wicket:id="komunikaty"></span>
<form wicket:id="daneOsobowe" action="">
<table>
<tr>
<td><wicket:message key="imie" />:</td>
<td colspan="2"><input type="text" wicket:id="imie" /></td>
</tr>
<tr>
<td><wicket:message key="nazwisko" />:</td>
<td colspan="2"><input type="text" wicket:id="nazwisko" /></td>
</tr>
<tr>
<td><wicket:message key="login" />:</td>
<td colspan="2"><input type="text" wicket:id="login" /></td>
</tr>
<tr>
<td><wicket:message key="miejscowosc" />:</td>
<td><select wicket:id="miejscowosc">
<option>Cokolwiek</option>
</select></td>
<td><input type="submit" value="Zatwierdź" /></td>
</tr>
</table>
</form>
</body>
</html>
i odpowiadający jej plik DaneOsobowe.properties z wartościami kluczy:
 imie=Imi\u0119
nazwisko=Nazwisko
login=Login
miejscowosc=Miejscowo\u015b\u0107
W zasadzie to plik DaneOsobowe.properties powinienem był nazwać DaneOsobowe_pl.properties, aby wskazać jaki język plik zawiera, ale dla prostoty przykłady postanowiłem zaniechać tego.

I to tyle. Teraz wystarczy przenieść wszystkie napisy do odpowiednich plików properties i stworzyć wiele ich wersji językowych dla każdego wspieranego przez aplikację języka. I to bez modyfikacji kodu źródłowego! Wprowadzenie obsługi wersji językowej dla kolejnego języka sprowadza się teraz do dodania kolejnego pliku properties.

Pytanie konkursowe: Jaka jest geneza akronimu i18n, który tłumaczy się jako umiędzynarodowienie? Nagród nie przewiduje się.

15 marca 2008

Apache Wicket z JPA z pomocą Spring Framework i Apache Maven 2

0 komentarzy
Kolejne wrażenia z pracy z Wicket w połączeniu z JPA i Spring Framework zaowocowały artykułem Apache Wicket z JPA z pomocą Spring Framework i Apache Maven 2 w moim Notatniku-Wiki. Dużo dobrej zabawy podczas zestawiania środowiska przypominającego serwer aplikacyjny Java EE z pomocą Spring Framework. Do tego wykorzystałem JPA do obsługi danych a całością zarządzał Apache Maven 2, co m.in. sprowadziło uaktualnienie Wicketa do wersji 1.3.2 do niewielkiej modyfikacji pom.xml. Zresztą podobnie było i ze Springiem, który również pojawił się w wersji 2.5.2. Najciekawszym elementem artykułu, jakkolwiek o Wicket, jest sama konfiguracja Springa, w taki sposób, że w żaden sposób nie wpłynął na inne pliki aplikacji. W zasadzie mógłbym wyrzucić Springa z aplikacji i nie wymagałoby to żadnej zmiany (poza klasą pl.jaceklaskowski.wicket.model.OsobaDaoImpl, gdzie użyłem adnotacji @Transactional, bez której babranie się z konfiguracją xmlową byłoby dla mnie marnotrawstem czasu). Ciekawym dodatkiem jest konfiguracja encji Osoba bez użycia adnotacji za pomocą pliku orm.xml. W ten sposób klasa encji w żaden sposób nie przypomina encji JPA. Jest to przykład bezinwazyjnej konfiguracji, gdzie istnienie pliku orm.xml potrafi zmienić zachowanie klasy. Poza tym konfiguracja Wicketa ze Springiem i dodanie JPA do pobrania danych z bazy danych HSQLDB uruchamianej w pamięci daje niezwykłe pole do popisu dla dalszych możliwych rozszerzeń w aplikacji. Zaczyna być niezwykle ciekawie. Zachęcam do lektury artykułu i przesyłanie komentarzy. Ciekawym ich bardzo.

W międzyczasie rozwiązano moje zgłoszenie WICKET-1333 Update wicket javadoc to 1.3.1 and place it on wicket.apache.org for stable referencing dotyczące stabilnego miejsca do publikacji javadoc Wicketa i teraz dostępne jest w menu Documentation > JavaDocs, tj. http://wicket.apache.org/docs/wicket-1.3.2/wicket/apidocs/index.html dla ostatniej wersji produkcyjnej 1.3.2.

03 marca 2008

Wicket Demo na OSGi z Felix i Pax Web

0 komentarzy
Wciąż rozpoznaję możliwe ścieżki wykorzystania OSGi w mojej aplikacji opartej o Apache Wicket. Pamiętam wskazówki Daniela, który wskazywał na Spring Dynamic Modules for OSGi Service Platforms, jednakże mimo, że już wprowadziłem Springa do aplikacji wciąż rozglądam się za alternatywnymi rozwiązaniami. Ot, tak na przekór, aby nie było na skróty.

Wczytując się w pRaSSówkę (wspaniałe określenie dla zbioru RSS za Polskim The Daily WTF) i posiłkując się Google natrafiłem na ciekawe dokonania w dziedzinie OSGi i tworzenia aplikacji webowych z Wicket. Trafiłem na Open Participation Software for Java, gdzie mnóstwo ciekawego oprogramowania związanego z OSGi, m.in. Pax Wicket (o czym nie będę jeszcze pisał, ale nie mogłem się oprzeć, aby już nie wspomnieć).

Projektem, który przyciągnął moją uwagę był Pax Web, czyli OSGi Http Service ze wsparciem dla wielu elementów specyfikacji Servlets, w tym i filtrów. Dlaczego wymieniłem filtry jako istotne? Przypominam, że Wicket pracuje oparty o filtr lub servlet, a w mojej przykładowej aplikacji skorzystałem z filtra. Stąd padło, że za dzisiejszy cel postanowiłem obrać uruchomienie demonstracyjnej aplikacji Wicketa z Pax Web.

Po zapoznaniu się z dokumentacją Pax Web (wyjątkowo niewiele acz treściwie) przyszło mi zdecydować o środowisku uruchomieniowym OSGi. Wybrałem Apache Felix. Poza nim miałem do wyboru Knoplerfisha bądź Eclipse Equinox. Z bardziej osobistych niż technicznych pobódek wybrałem Felix. Pobrałem Felix 1.0.3.

Rozpocząłem od uruchomienia Pax Web na Felixie. Uruchomienie Felixa i instalacja opisane są w Apache Felix Usage Documentation.
 jlaskowski@dev /cygdrive/c/apps/felix
$ java -jar bin/felix.jar

Welcome to Felix.
=================

Enter profile name: wicket

DEBUG: WIRE: 1.0 -> org.osgi.service.packageadmin -> 0
DEBUG: WIRE: 1.0 -> org.osgi.service.startlevel -> 0
DEBUG: WIRE: 1.0 -> org.ungoverned.osgi.service.shell -> 1.0
DEBUG: WIRE: 1.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 1.0 -> org.apache.felix.shell -> 1.0
DEBUG: WIRE: 2.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 2.0 -> org.apache.felix.shell -> 1.0
DEBUG: WIRE: 3.0 -> org.osgi.service.obr -> 3.0
DEBUG: WIRE: 3.0 -> org.osgi.framework -> 0
-> DEBUG: WIRE: 3.0 -> org.apache.felix.shell -> 1.0

-> version
1.0.3
-> ps
START LEVEL 1
ID State Level Name
[ 0] [Active ] [ 0] System Bundle (1.0.3)
[ 1] [Active ] [ 1] Apache Felix Shell Service (1.0.0)
[ 2] [Active ] [ 1] Apache Felix Shell TUI (1.0.0)
[ 3] [Active ] [ 1] Apache Felix Bundle Repository (1.0.2)
Podczas uruchomienia Felixa, podawana jest nazwa profilu, która jest nazwą zbioru zainstalowanych pakunków tak, że kolejne uruchomienie Felixa z tą nazwą zainstaluje i uruchomi dokładnie te pakunki zawarte w zadanym profilu. W praktyce ów profil jest po prostu katalogiem pakunków w katalogu domowym użytkownika.
 jlaskowski@dev /cygdrive/c/apps/felix
$ ls -l c\:/Documents\ and\ Settings/jlaskowski/.felix/wicket/
total 0
d---------+ 2 jlaskowski None 0 Mar 2 23:02 bundle0
d---------+ 3 jlaskowski None 0 Mar 2 23:02 bundle1
d---------+ 3 jlaskowski None 0 Mar 2 23:02 bundle2
d---------+ 3 jlaskowski None 0 Mar 2 23:02 bundle3
Uruchomienie Pax Web to wykonanie poleceń install oraz start z pobranym pax-web-service (pax-web-service-0.3.1.jar)
 -> install file:/C:/apps/pax-web/pax-web-service-0.3.1.jar
Bundle ID: 4
-> start 4
org.osgi.framework.BundleException: Unresolved package in bundle 4:
package; (&(package=org.osgi.service.http)(version>=1.0.0)(!(version>=2.0.0)))
I tutaj pierwsza niespodzianka. Niespełniona zależność od pakietu org.osgi.service.http. Dzięki Creating a jetty based OSGi HttpService for apache felix z dnia 29.02.2008 (!) dowiedziałem się, że potrzebuję pobrać i zainstalować również pakunek OSGi compendium API's Feliksa - org.osgi.compendium-1.0.0.jar.
 -> install file:/C:/apps/felix/bundle/org.osgi.compendium-1.0.0.jar
Bundle ID: 5
-> start 5
DEBUG: WIRE: 5.0 -> javax.servlet.http -> 4.0
DEBUG: WIRE: 5.0 -> javax.servlet -> 4.0
DEBUG: WIRE: 5.0 -> javax.xml.parsers -> 0
DEBUG: WIRE: 5.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 4.0 -> javax.servlet.http -> 4.0
DEBUG: WIRE: 4.0 -> org.xml.sax -> 0
DEBUG: WIRE: 4.0 -> javax.servlet.resources -> 4.0
DEBUG: WIRE: 4.0 -> org.osgi.service.http -> 5.0
DEBUG: WIRE: 4.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 4.0 -> org.osgi.service.cm -> 5.0
DEBUG: WIRE: 4.0 -> javax.servlet -> 4.0
DEBUG: WIRE: 4.0 -> org.xml.sax.helpers -> 0
DEBUG: WIRE: 4.0 -> javax.xml.parsers -> 0
DEBUG: WIRE: 4.0 -> org.osgi.util.tracker -> 0
DEBUG: WIRE: 4.0 -> javax.servlet.jsp.resources -> 4.0
DEBUG: WIRE: 4.0 -> javax.net.ssl -> 0
DEBUG: WIRE: 4.0 -> org.ops4j.pax.web.service -> 4.0
-> start 4
2008-03-02 23:42:54.096::INFO: Logging to STDERR via org.mortbay.log.StdErrLog
2008-03-02 23:42:54.142::INFO: jetty-6.1.x
2008-03-02 23:42:54.174::INFO: Started SocketConnectorWrapper@0.0.0.0:8080
Tym razem Pax Web rozpoczął pracę bez zgłaszania braku zależności i Jetty 6.1 ruszył na porcie 8080.

Pytanie jakie sobie zadawałem, to: Dobrze mam uruchomionego Jetty, ale co z moją aplikacją? Jak mogę ją zainstalować? Każdy kto "dotknie" OSGi wie, że cokolwiek nie pojawi się w tym środowisku musi być wprost lub niewprost pakunkiem. Oznaczało to, że aplikacja webowa musiałaby w jakiś sposób stać się pakunkiem. Nie jest to wyczyn sam w sobie, bo opisywałem to już w Pakunki OSGi w projekcie wielomodułowym Apache Maven 2 z maven-bundle-plugin czy Tworzenie pakietów OSGi z Apache Maven 2, ale jak sprawić, aby pakunek aplikacji webowej był właśnie aplikacją webową i to jeszcze rozpoznaną przez właśnie uruchomionego Jetty. Na pewno musiałaby pojawić się jakaś zależność między nimi, ale jak ją określić?!

Z pomocą przychodzi Pax Web Extender - War, czyli kolejny pakunek z OPS4J, który sprawia, że uruchomienie aplikacji webowych staje się trywialne. Wystarczy pobrać pax-web-ex-war-0.3.0.jar i uruchomić.
 -> install file:/C:/apps/pax-web/pax-web-ex-war-0.3.0.jar
Bundle ID: 6
-> start 6
DEBUG: WIRE: 6.0 -> javax.servlet.http -> 4.0
DEBUG: WIRE: 6.0 -> javax.servlet -> 4.0
DEBUG: WIRE: 6.0 -> org.xml.sax -> 0
DEBUG: WIRE: 6.0 -> org.osgi.service.http -> 5.0
DEBUG: WIRE: 6.0 -> javax.xml.parsers -> 0
DEBUG: WIRE: 6.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 6.0 -> org.w3c.dom -> 0
DEBUG: WIRE: 6.0 -> org.osgi.util.tracker -> 0
DEBUG: WIRE: 6.0 -> org.ops4j.pax.web.service -> 4.0
Pakunek Pax Web Extender uruchomiony. A teraz co? Znowu dokumentacja, a tam instrukcja jak bez żadnych modyfikacji do aplikacji webowej uruchomić ją na OSGi, czyli Pax URL - war. Kolejny pakunek do uruchomienia.
 -> install file:/C:/apps/pax-web/pax-url-war-0.2.1.jar
Bundle ID: 7
-> start 7
DEBUG: WIRE: 7.0 -> org.osgi.service.url -> 0
DEBUG: WIRE: 7.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 7.0 -> org.ops4j.pax.url.war -> 7.0
DEBUG: WIRE: 7.0 -> javax.xml.transform.stream -> 0
DEBUG: WIRE: 7.0 -> org.osgi.service.cm -> 5.0
DEBUG: WIRE: 7.0 -> javax.xml.transform -> 0
DEBUG: WIRE: 7.0 -> javax.net.ssl -> 0
Teraz wystarczy po prostu uruchomić aplikację webową, np. http://repo1.maven.org/maven2/org/apache/wicket/wicket-examples/1.3.1/wicket-examples-1.3.1.war.
 -> install 
war:http://repo1.maven.org/maven2/org/apache/wicket/wicket-examples/1.3.1/wicket-examples-1.3.1.war
Bundle ID: 8
-> start 8
DEBUG: WIRE: 8.0 -> javax.servlet.http -> 4.0
DEBUG: WIRE: 8.0 -> javax.sql.rowset -> 0
DEBUG: WIRE: 8.0 -> javax.xml.namespace -> 0
DEBUG: WIRE: 8.0 -> javax.crypto -> 0
DEBUG: WIRE: 8.0 -> javax.xml.transform -> 0
DEBUG: WIRE: 8.0 -> javax.naming -> 0
DEBUG: WIRE: 8.0 -> javax.servlet -> 4.0
DEBUG: WIRE: 8.0 -> javax.xml.parsers -> 0
DEBUG: WIRE: 8.0 -> javax.management.modelmbean -> 0
DEBUG: WIRE: 8.0 -> javax.xml.transform.stream -> 0
DEBUG: WIRE: 8.0 -> javax.swing -> 0
DEBUG: WIRE: 8.0 -> javax.management -> 0
DEBUG: WIRE: 8.0 -> javax.swing.border -> 0
DEBUG: WIRE: 8.0 -> javax.management.remote -> 0
DEBUG: WIRE: 8.0 -> javax.swing.event -> 0
DEBUG: WIRE: 8.0 -> org.xml.sax.helpers -> 0
DEBUG: WIRE: 8.0 -> javax.swing.tree -> 0
DEBUG: WIRE: 8.0 -> javax.swing.table -> 0
DEBUG: WIRE: 8.0 -> javax.sql -> 0
DEBUG: WIRE: 8.0 -> javax.crypto.spec -> 0
DEBUG: WIRE: 8.0 -> javax.rmi.CORBA -> 0
DEBUG: WIRE: 8.0 -> javax.transaction -> 0
DEBUG: WIRE: 8.0 -> javax.swing.text -> 0
DEBUG: WIRE: 8.0 -> org.xml.sax -> 0
DEBUG: WIRE: 8.0 -> javax.imageio -> 0
DEBUG: WIRE: 8.0 -> org.w3c.dom -> 0
DEBUG: WIRE: 8.0 -> javax.xml.transform.dom -> 0
DEBUG: WIRE: 8.0 -> javax.rmi -> 0
...
INFO - WebApplication - [TemplateApplication] Started Wicket 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. ***
********************************************************************
...
->
Aplikacja wydaje się uruchomiona. Pozostaje sprawdzić ją w działaniu. Ale moment, a jaki adres aplikacji? Chciałoby się, aby było to podobnie jak to ma miejsce w typowym środowisku kontenera servletów, gdzie nazwa pliku wskazuje na nazwę kontekstu. W tym przypadku tak nie jest i nazwa aplikacji obliczana jest na podstawie parametru przekazanego po war, czyli w tym przypadku będzie to:
 http://localhost:8080/http___repo1.maven.org_maven2_org_apache_wicket_wicket-examples_1.3.1_wicket-examples-1.3.1.war
Pytanie tylko skąd o tym się dowiedzieć, bo przecież mimo dokumentacji Instructions file syntax nie wyliczyłem jej zamieniając odpowiednie znaki.

Nazwa kontekstu kryje się w Bundle-SymbolicName pakunku. Sprawdzam jaką konfigurację ma pakunek o identyfikatorze 8, który jest zainstalowaną aplikacją.
 -> headers 8

Bundle 8
--------
Generated-By-Ops4j-Pax-From =
http://repo1.maven.org/maven2/org/apache/wicket/wicket-examples/1.3.1/wicket-examples-1.3.1.war
Bundle-ClassPath = .,WEB-INF/classes,WEB-INF/lib/...
Tool = Bnd-unknown version
Created-By = 1.5.0_14 (Sun Microsystems Inc.)
Bnd-LastModified = 1204529294312
WAR-URL =
http://repo1.maven.org/maven2/org/apache/wicket/wicket-examples/1.3.1/wicket-examples-1.3.1.war
Built-By = fb
Originally-Created-By = Apache Maven
Bundle-Version = 0
Build-Jdk = 1.5.0_13
Manifest-Version = 1.0
Bundle-ManifestVersion = 2
Archiver-Version = Plexus Archiver
Import-Package = javax.activation;resolution:=...
Bundle-SymbolicName =
http___repo1.maven.org_maven2_org_apache_wicket_wicket-examples_1.3.1_wicket-examples-1.3.1.war

Interesująca nas właściwość to Bundle-SymbolicName.


Pozostaje sprawdzić instalację mojej demonstracyjnej aplikacji webowej - wicket-demo.
 -> install war:file:/c:/projs/sandbox/wicket-demo/target/wicket-demo-1.0-SNAPSHOT.war
Bundle ID: 10
-> start 10
DEBUG: WIRE: 10.0 -> javax.servlet.http -> 4.0
DEBUG: WIRE: 10.0 -> javax.sql.rowset -> 0
DEBUG: WIRE: 10.0 -> javax.xml.namespace -> 0
DEBUG: WIRE: 10.0 -> javax.crypto -> 0
DEBUG: WIRE: 10.0 -> javax.xml.transform -> 0
DEBUG: WIRE: 10.0 -> javax.naming -> 0
DEBUG: WIRE: 10.0 -> javax.servlet -> 4.0
DEBUG: WIRE: 10.0 -> javax.xml.parsers -> 0
DEBUG: WIRE: 10.0 -> javax.xml.transform.stream -> 0
DEBUG: WIRE: 10.0 -> javax.management.modelmbean -> 0
DEBUG: WIRE: 10.0 -> javax.swing -> 0
DEBUG: WIRE: 10.0 -> javax.management -> 0
DEBUG: WIRE: 10.0 -> javax.swing.border -> 0
DEBUG: WIRE: 10.0 -> javax.transaction.xa -> 0
DEBUG: WIRE: 10.0 -> javax.management.remote -> 0
DEBUG: WIRE: 10.0 -> javax.swing.event -> 0
DEBUG: WIRE: 10.0 -> org.xml.sax.helpers -> 0
DEBUG: WIRE: 10.0 -> javax.swing.tree -> 0
DEBUG: WIRE: 10.0 -> javax.swing.table -> 0
DEBUG: WIRE: 10.0 -> javax.management.openmbean -> 0
DEBUG: WIRE: 10.0 -> javax.sql -> 0
DEBUG: WIRE: 10.0 -> javax.crypto.spec -> 0
DEBUG: WIRE: 10.0 -> javax.rmi.CORBA -> 0
DEBUG: WIRE: 10.0 -> javax.transaction -> 0
DEBUG: WIRE: 10.0 -> javax.swing.text -> 0
DEBUG: WIRE: 10.0 -> org.xml.sax -> 0
DEBUG: WIRE: 10.0 -> javax.imageio -> 0
DEBUG: WIRE: 10.0 -> org.w3c.dom -> 0
DEBUG: WIRE: 10.0 -> javax.rmi -> 0
-> headers 10

Bundle 10
---------
Generated-By-Ops4j-Pax-From =
file:/c:/projs/sandbox/wicket-demo/target/wicket-demo-1.0-SNAPSHOT.war
Bundle-ClassPath = .,WEB-INF/classes,WEB-INF/lib/...
Tool = Bnd-unknown version
Created-By = 1.5.0_14 (Sun Microsystems Inc.)
Bnd-LastModified = 1204531073078
WAR-URL =
file:/c:/projs/sandbox/wicket-demo/target/wicket-demo-1.0-SNAPSHOT.war
Built-By = jlaskowski
Originally-Created-By = Apache Maven
Bundle-Version = 0
Build-Jdk = 1.5.0_14
Manifest-Version = 1.0
Bundle-ManifestVersion = 2
Archiver-Version = Plexus Archiver
Import-Package = javax.activation;resolution:=...
Bundle-SymbolicName =
file__c__projs_sandbox_wicket-demo_target_wicket-demo-1.0-SNAPSHOT.war
Niestety z jakiś nieznanych mi powodów moja aplikacja nie pozwoliła się uruchomić. Zostawiam to na później.
 -> shutdown
-> INFO - Application - [EchoApplication] destroy: Wicket JMX initializer
INFO - Application - [GuestBookApplication] destroy: Wicket JMX initializer
INFO - Application - [FormInputApplication] destroy: Wicket JMX initializer
INFO - Application - [UnicodeConverterApplication] destroy: Wicket JMX initializer
INFO - Application - [SignInApplication] destroy: Wicket JMX initializer
INFO - Application - [PrototypeApplication] destroy: Wicket JMX initializer
INFO - Application - [ImagesApplication] destroy: Wicket JMX initializer
INFO - Application - [AjaxApplication] destroy: Wicket JMX initializer
INFO - Application - [HangmanApplication] destroy: Wicket JMX initializer
INFO - Application - [StatelessApplication] destroy: Wicket JMX initializer
INFO - Application - [BreadCrumbApplication] destroy: Wicket JMX initializer
INFO - Application - [NiceUrlApplication] destroy: Wicket JMX initializer
INFO - Application - [MyAuthenticatedWebApplication] destroy: Wicket JMX initializer
INFO - Application - [ExampleApplication] destroy: Wicket JMX initializer
INFO - Application - [GuiceApplication] destroy: Wicket JMX initializer
INFO - Application - [CaptchaApplication] destroy: Wicket JMX initializer
INFO - Application - [RolesApplication] destroy: Wicket JMX initializer
INFO - Application - [LibraryApplication] destroy: Wicket JMX initializer
INFO - Application - [HelloBrowserApplication] destroy: Wicket JMX initializer
INFO - Application - [VelocityTemplateApplication] destroy: Wicket JMX initializer
INFO - Application - [WizardApplication] destroy: Wicket JMX initializer
INFO - Application - [WicketExamplesMenuApplication] destroy: Wicket JMX initializer
INFO - Application - [ComponentReferenceApplication] destroy: Wicket JMX initializer
INFO - Application - [NavomaticApplication] destroy: Wicket JMX initializer
INFO - Application - [DatesApplication] destroy: Wicket JMX initializer
INFO - Application - [Application] destroy: Wicket JMX initializer
INFO - Application - [StockQuoteApplication] destroy: Wicket JMX initializer
INFO - Application - [NestedApplication] destroy: Wicket JMX initializer
INFO - Application - [PubApplication] destroy: Wicket JMX initializer
INFO - Application - [RepeaterApplication] destroy: Wicket JMX initializer
INFO - Application - [UploadApplication] destroy: Wicket JMX initializer
INFO - Application - [FramesApplication] destroy: Wicket JMX initializer
INFO - Application - [CustomResourceLoadingApplication] destroy: Wicket JMX initializer
INFO - Application - [LinkomaticApplication] destroy: Wicket JMX initializer
INFO - Application - [HelloWorldApplication] destroy: Wicket JMX initializer
INFO - Application - [SignIn2Application] destroy: Wicket JMX initializer
INFO - Application - [TemplateApplication] destroy: Wicket JMX initializer
INFO - Application - [EncodingsApplication] destroy: Wicket JMX initializer
INFO - Application - [PubApplication] destroy: Wicket JMX initializer
2008-03-03 00:01:28.468:/http___repo1.maven.org_maven2_org_apache_wicket_wicket-examples_1.3.1_wicket-example
INFO - XmlWebApplicationContext - Closing application context [Root WebApplicationContext]
INFO - DefaultListableBeanFactory - Destroying singletons in {org.springframework.beans.factory.support.Defa
f BeanFactory hierarchy}
W dowód wdzięczności zgłosiłem błąd - (PAXWEB-84) NPE after a web app (bundle) is stopped, który został już naprawiony (!) To nazywa się współpraca.

26 lutego 2008

Wicket 1.3.1 i Spring Framework 2.5.1 jednak współpracują

3 komentarzy
Tak niewiele było trzeba, aby przekonać Apache Wicket 1.3.1 do współpracy ze Spring Framework 2.5.1. Wczorajszy wpis "Globalizacja" obiektów Wicketa ze Spring Framework wzbudził zainteresowanie Waldiego, który swoim komentarzem z kolei sprowokował mnie do głębszego zastanowienia nad sposobem uaktualnienia trochę już zakurzonej zależności Wicket 1.3.1 od Spring Framework 2.0. Czy to nie-mavenowe rozwiązanie Waldka czy po prostu jego wizyta w moim Notatniku, ale to wystarczyło, abym wpadł na to właściwe mavenowe rozwiązanie. Coraz częściej daje się słyszeć utyskiwania na zarządzanie zależnościami przez Apache Maven 2, ale w tej sytuacji spisał się zgodnie z oczekiwaniami. Uruchomienie Spring Framework 2.5.1 oraz Wicket 1.3.1 sprowadza się do następującej zmiany w pom.xml w projekcie mojej demonstracyjnej aplikacji:
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-spring</artifactId>
<version>${wicket.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.1</version>
</dependency>
Najistotniejszą zmianą jest wykluczenie zależności org.springframework.spring z zależności org.apache.wicket.wicket-spring za pomocą elementu exclusions i samodzielne dopisanie zależności org.springframework.spring.2.5.1 do projektu. Tyle! Dziękuję Waldek za inspirację! Czasami trzeba zwykłego pytania/sugestii/po prostu pogadać z kimś kto się tym również para/interesuje, aby rozwiązać zadanie w parę sekund.

Dla upiększenia przykładu dodałem wyświetlenie wersji Springa w etykiecie na stronie do uwierzytelnienia użytkownika. Zmiana w kodzie klasy-strony pl.jaceklaskowski.wicket.PrzedstawSie:
// wyświetl wykorzystywaną wersję Spring Framework
loginForm.add(new Label("springVersion", SpringVersion.getVersion()));
Zmiana wymagała dodania odpowiadającemu etykiecie elementowi span na stronie HTML - PrzedstawSie.html:
<tr>
<td colspan="3">
Wersja Spring Framework: <b><span wicket:id="springVersion">Wersja Spring Framework</span></b>
</td>
</tr>
Uruchomienie aplikacji z tą zmianą wyświetliło poprawną wersję Springa.


Takiej inspiracji mi trzeba było! Z pozdrowieniami dla Waldka!

25 lutego 2008

"Globalizacja" obiektów Wicketa ze Spring Framework

5 komentarzy
Mimo tak szumnego tytułu zacznę lekko, od wtyczki Wicket Bench. Podczas mojej pracy z Eclipse i projektem wicketowym całkowicie o niej zapomniałem. Podczas tworzenia klasy-strony PrzedstawSie w mojej demonstracyjnej aplikacji nieoczekiwanie pojawiła się podpowiedź odnośnie możliwości utworzenia odpowiadającej jej strony html.


Nie ukrywam, że mile mnie zaskoczyła nadgorliwość wtyczki do skracania czasu potrzebnego do tworzenia kolejnej strony, a że miałem zabrać się za HTML, więc rychło skorzystałem z pomocy. Jakież było moje zaskoczenie, kiedy moim oczom ukazała się strona następującej treści:
<html xmlns:wicket>
<wicket:panel>

</wicket:panel>
</html>
Pomyślałem, że to z powodu, że nic nie było w samej klasie. Szybko uzupełniłem (oprogramowałem) klasę o potrzebne mi komponenty i po skasowaniu strony HTML, wykonałem generacje strony ponownie. Niestety, ale efekt końcowy nie zmienił się, ani na jotę. Spodziewałem się więcej. Na razie odpuszczam ją sobie, chociaż wciąż w tle próbuje mi pomóc.

Wciąż część mojego czasu poświęcam na lekturę książki Pro Wicket z Manning i wciąż ten sam rozdział 3. Developing a Simple Application. Ten rozdział zacznie mi się niedługo śnić. Po ostatnim moim wpisie odnośnie sesji - Sesja i przekierowanie żądania w Wicket - przyszła pora na wzmiankę o zasięgu aplikacyjnego - przestrzeni obiektów globalnych aplikacji. Krótka wizyta na stronie dokumentacji klasy org.apache.wicket.markup.repeater.data.IDataProvider, a tam oto taki przykład pomocniczy:
class UsersProvider implements IDataProvider {

public Iterator iterator(int first, int count) {
((MyApplication)Application.get()).getUserDao().iterator(first, count);
}

public int size() {
((MyApplication)Application.get()).getUserDao().getCount();
}

public IModel model(Object object) {
return new DetachableUserModel((User)object);
}
}
Pomijając wartość płynącą z IDataProvider przyjrzyjmy się konstrukcji
((MyApplication)Application.get()).getUserDao()
, która zwraca pewne DAO (w tym przypadku UserDao)...globalne swoim zasięgiem. Właśnie w ten sposób realizuje się zasięg application znany z JSF czy JSP/Servlets w wykonaniu Wicket.

Przypominając jak działała sesja w Wicket można zauważyć podobieństwo między nimi w sposobie ich pozyskiwania. I sesję, i aplikację pobieramy za pomocą konstrukcji PewnienBytWicketa.get() z rzutowaniem na właściwy typ. Tak było z sesją - Session.get() i tak jest z aplikacją - Application.get(). Jeśli zatem przyjdzie nam umieścić pewnien byt w przestrzeni obiektów o zasięgu aplikacyjnym (application) wystarczy dostarczyć odpowiednie metody do naszej klasy aplikacyjnej (u mnie będzie to pl.jaceklaskowski.wicket.WicketDemoApplication) i na tym sprawa się kończy. Podobnie jak miało to miejsce przy obiektach sesyjnych, mamy dostarczone silne typowanie za darmo, tzn. w czasie proporcjonalnym do naszego nakładu pracy przy utworzeniu atrybutu zadanego typu.
public class WicketDemoApplication extends WebApplication {

private Logger logger = Logger.getLogger(WicketDemoApplication.class.getName());

private SlowoDao slowoDao;

...

public SlowoDao getSlowoDao() {
return slowoDao;
}

public void setSlowoDao(SlowoDao slowoDao) {
this.slowoDao = slowoDao;
}
}
Nie wiedzieć czemu, kiedykolwiek widzę DAO na myśl przychodzi mi Spring Framework. Skoro już przy nim jestem spojrzenie jak to mógłby on nas wesprzeć w tworzeniu aplikacji z Wicket (a wiem, że może, więc pewnie oczekuję tej integracji tak pewnie). Skok do rozdziału 5. Integration with Other Frameworks w Pro Wicket, gdzie odszukałem niezbędne informacje o integracji Wicket-Spring.

Krok 1. Zmieniamy konfigurację aplikacji webowej celem zarejestrowania obiektu nasłuchującego realizującego integrację między Wicketem a Springiem.
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
oraz dodaję parametr konfiguracyjny dla wicketowego servletu lub (jak w mojej demonstracyjnej aplikacji) filtra.
<init-param>
<param-name>applicationFactoryClassName</param-name>
<param-value>wicket.spring.SpringWebApplicationFactory</param-value>
</init-param>
Dodatkowo usuwam parametr
<init-param>
<param-name>applicationClassName</param-name>
<param-value>pl.jaceklaskowski.wicket.WicketDemoApplication</param-value>
</init-param>
który będzie wskazany przez konfigurację Springa w jego domyślnie poszukiwanym pliku konfiguracyjnym /WEB-INF/applicationContext.xml lub dowolnym innym pliku konfiguracyjnym wskazanym przez parametr konfiguracyjny wicketowego servletu/filtra przez parametr contextConfigLocation.

Ostatecznie kończę zmiany z następującym plikiem /WEB-INF/web.xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
<display-name>wicket-demo</display-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>wicket.wicket-demo</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationFactoryClassName</param-name>
<param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>wicket.wicket-demo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Krok 2: Utworzenie konfiguracji dla Spring Framework z Wicketem

Krótka wizyta na stronie dokumentacji Spring Framework - Chapter 3. The IoC container i mamy gotowy plik /WEB-INF/applicationContext.xml o następującej treści:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="slowoDao" class="pl.jaceklaskowski.wicket.model.SlowoDao" />
<bean id="wicketApplication" class="pl.jaceklaskowski.wicket.WicketDemoApplication">
<property name="slowoDao" ref="slowoDao" />
</bean>
</beans>
I tutaj olśnienie - spostrzegłem, że do tej pory wszystkie moje encje były zawarte w pakiecie pl.jaceklaskowski.wicket.entities, podczas gdy trafniejszym byłby pl.jaceklaskowski.wicket.model, który nie wskazywałby na technologię, ale na rolę klas w nim zawartych. Zmieniłem i wdrażam do codziennego użycia.

Krok 3. (opcjonalny) Aktualizacja pom.xml dla projektu mavenowego

Kolejny raz dostrzegam zalety stosowania Maven do zarządzania moim projektem, tak że dodanie wymaganych bibliotek Spring Framework sprowadza się do dodania następującej sekcji dependency do pom.xml:
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-spring</artifactId>
<version>${wicket.version}</version>
</dependency>
Wystarczy, więc dodanie zależności org.apache.wicket.wicket-spring i potrzebne zależności, w tym i sam Spring Framework, zostaną pobrane dzięki uprzejmości Mavena.

Parametr ${wicket.version} to już zadanie dla Maven, który rozwiązuje mi ją do wersji 1.3.1 (co, jak i dlaczego w tym temacie było na samym początku moich zmagań z Wicket - Pierwsze kroki z Apache Wicket 1.3).

Podczas uruchomienia tak zmodyfikowanej aplikacji webowej obiekt nasłuchujący uruchomienia aplikacji zarejestrowany w jej deskryptorze - org.springframework.web.context.ContextLoaderListener zainicjuje infrastrukturę Spring Framework. Korzystamy wyłącznie z funkcjonalności IoC Springa, więc ziarna (wskazane przez elementy beans w pliku applicationContext.xml) zostaną zainicjowane i wstrzelone, gdzie wskazano, np. WicketDemoApplication zostanie "zasilone" egzemplarzem SlowoDao poprzez publiczną metodę zapisu setSlowoDao(SlowoDao slowoDao). Następnie podczas inicjowania Wicket (w naszej konfiguracji podczas uruchomienia filtra) nastąpi uruchomienie fabryki klasy aplikacji wskazanej przez parametr applicationFactoryClassName, tj. org.apache.wicket.spring.SpringWebApplicationFactory. Fabryka SpringWebApplicationFactory już wie, czego należy szukać w "przestrzeni" springowej, aby uruchomić aplikację. Już wiadomo, że potrzebna jest klasa rozszerzająca (wprost bądź niewprost) klasę org.apache.wicket.protocol.http.WebApplication, co w tym przypadku jest jednoznacznym wskazaniem na pl.jaceklaskowski.wicket.WicketDemoApplication.

W dokumentacji klasy org.apache.wicket.spring.SpringWebApplicationFactory napisano, że w przypadku wielu aplikacji wicketowych zdefiniowanych w "przestrzeni" Springa można wskazać tę jedną, wybraną przez parametr beanName. Trudno mi teraz to wytłumaczyć, ale natchnęło mnie na przejrzenie źródeł i...strzał w plecy - dokumentacja klasy jest niepoprawna (!) Okazuje się, że należy skorzystać z parametru applicationBean (kolejny raz, kiedy potwierdza się stara dobra maksyma, że warto zaglądać do kodu źródłowego, jeśli się go ma i ma się czas na jego lekturę):
<init-param>
<param-name>applicationBean</param-name>
<param-value>wicketApplication</param-value>
</init-param>
To może skłaniać do pytania, czy nazwa aplikacji wicketowej w applicationContext (poprzez atrybut id) ma znaczenie. Otóż nie. Poszukiwanie tej jednej wybranej aplikacji wicketowej bez określenia jej przez beanName jest realizowane przez odszukanie ziaren (springowych) rozszerzających org.apache.wicket.protocol.http.WebApplication.

I tutaj uwaga. Jeśli ktokolwiek pomyślałby o zadeklarowaniu zależności Spring Framework 2.5.1 w aplikacji opartej o Wicket 1.3.1 może na starcie spodziewać się poniższego komunikatu błędu:
2008-02-24 20:40:23.447::INFO:  jetty-6.1.7
2008-02-24 20:40:23.588::INFO: No Transaction manager found - if your webapp requires one,
please configure one.
2008-02-24 20:40:24.947::WARN:
failed org.mortbay.jetty.plugin.Jetty6PluginWebAppContext@1083717
{/wicket-demo,C:\projs\sandbox\wicket-demo\src\main\webapp}
java.lang.NoSuchMethodError:
org.springframework.core.CollectionFactory.createConcurrentMapIfPossible(I)Ljava/util/Map;
at org.springframework.web.context.ContextLoader.(ContextLoader.java:153)
at org.springframework.web.context.ContextLoaderListener.createContextLoader
(ContextLoaderListener.java:53)
at org.springframework.web.context.ContextLoaderListener.contextInitialized
(ContextLoaderListener.java:44)
at org.mortbay.jetty.handler.ContextHandler.startContext(ContextHandler.java:540)
at org.mortbay.jetty.servlet.Context.startContext(Context.java:135)
at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1220)
at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:510)
at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:448)
at org.mortbay.jetty.plugin.Jetty6PluginWebAppContext.doStart
(Jetty6PluginWebAppContext.java:110)
at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
at org.mortbay.jetty.handler.HandlerCollection.doStart(HandlerCollection.java:152)
Wicketowy org.apache.wicket.wicket-spring.1.3.1 deklaruje już zależność od Spring Framework, więc należy usunąć własną, bo...za nowa. Wersja Spring Framework zadeklarowana jako zależność dla modułu wicket-spring (org.apache.wicket.wicket-parent.1.3.1) to 2.0 i jak widać jest pewna rozbieżność między nimi w publicznym interfejsie Springa, z którego korzysta Wicket.

Pora uruchomić aplikację.
2008-02-24 20:44:18.036::INFO:  jetty-6.1.7
2008-02-24 20:44:18.176::INFO: No Transaction manager found - if your webapp requires one,
please configure one.
INFO - ContextLoader - Root WebApplicationContext: initialization started
2008-02-24 20:44:19.520:/wicket-demo:INFO: Loading Spring root WebApplicationContext
INFO - CollectionFactory - JDK 1.4+ collections available
INFO - XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resource
[/WEB-INF/applicationContext.xml]
INFO - XmlWebApplicationContext - Bean factory for application context [Root WebApplicationContext]:
org.springframework.beans.factory.support.DefaultListableBeanFactory defining
beans [slowoDao,wicketApplication]; root of BeanFactory hierarchy
INFO - XmlWebApplicationContext - 2 beans defined in application context [Root WebApplicationContext]
INFO - XmlWebApplicationContext - Unable to locate MessageSource with name 'messageSource':
using default [org.springframework.context.support.DelegatingMessageSource@5ead9d]
INFO - XmlWebApplicationContext - Unable to locate ApplicationEventMulticaster with name
'applicationEventMulticaster': using default
[org.springframework.context.event.SimpleApplicationEventMulticaster@1a93f38]
INFO - UiApplicationContextUtils - Unable to locate ThemeSource with name 'themeSource':
using default [org.springframework.ui.context.support.ResourceBundleThemeSource@29d75]
INFO - DefaultListableBeanFactory - Pre-instantiating singletons in factory
[org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans
[slowoDao,wicketApplication]; root of BeanFactory hierarchy]
INFO - ContextLoader - Using context class
[org.springframework.web.context.support.XmlWebApplicationContext] for root WebApplicationContext
INFO - ContextLoader - Root WebApplicationContext: initialization completed in 1391 ms
INFO - Application - [WicketDemoApplication] init: Wicket core library initializer
...
INFO - WebApplication - [WicketDemoApplication] Started Wicket version 1.3.1 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. ***
********************************************************************
2008-02-24 20:44:21.254::INFO: Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
Aplikacja wystartowała poprawnie! Sprawdzenie w przeglądarce i po chwili już wiem, że wszystko w porządku (tutaj kolejny raz przydałoby się wsparcie Selenium czy podobnie do wykonania testów za mnie - książka An Introduction to Testing Web Applications with twill and Selenium z OReilly już czeka na mnie, jak i Tomasz Kaczanowski z prezentacją na marcowym spotkaniu grupy Warszawa JUG, na którym mnie jeszcze nie będzie).

Na zakończenie kilka ciekawostek, na które dzisiaj natrafiłem w tak zwanym międzyczasie. W trakcie przeglądania mojego zbioru blogów zacząłem od naszych, lokalnych i najciekawszym wydał mi się wpis Jak nie należy programować w polskim The Daily WTF, w którym kluczowym był fragment:

Tydzień z grubsza zajęło mi doprowadzenie do używalności kodu, który otrzymałem w spadku po pewnym urlopowiczu. Dobrze, że nie mam włosów, bo z pewnością bym je dawno wyrwał, choć z — drugiej strony — na programowaniu cierpi moja broda. Kolega wypoczywa w Iranie, a my próbujemy dojść, co też podmiot liryczny miał na myśli. Kiedy czytanie źródeł dłuży się ponad lekturę Nad Niemnem, znak to, że czas na notkę.

Szczególnie ten moment z "podmiot liryczny" jest na 5+. Dobrze, że nic nie piłem, bo parsknąłem, kiedy na niego trafiłem. Ładnie wyglądałby mój laptop. Gratuluję poczucia humoru nawet w tak trudnych chwilach jak analiza czyjegoś kodu źródłowego! Czy ja dzisiaj nie miałem podobnej eskapady?! Szczęśliwie moja lektura obyła się bez wyrywania włosów (wciąż je mam gotowe do przycinki) i wyrywania brody (tego nie mam i nie miałem).

Przy okazji analizy blogów natrafiłem na wpis JSF vs Wicket, Job Opportunities. Znowu pod temat dzisiejszego mojego wpisu o Wicket (czy oni się przypadkiem nie skrzyknęli? ;-)). Na razie Wicket daleko w tyle, ale czuję, że podobnie jak w moim przypadku, wszystko za sprawą owego ciała standaryzującego Korporacyjną Javę, gdzie JSF jest jednym z kluczowych graczy i w ogóle całego nagłaśniania ważności technologii Java EE (również i przeze mnie). Wielu z nas, nie ma czasu, chęci, wpisz dowolny powód na brak własnego rozwoju technologicznego i kiedy pozna jedną technologię przykuwa się do niej grubym łańcuchem, aby albo ona wyniosła nas na wyżyny intelektualne, albo my ją. Tak, czy owak nie spotykam się często z przeniesieniem swoich uczuć na inne technologie w ramach samej dżawki (i nie piszę tu o migracji do całkowicie innej platformy jak np. .Net). To wydaje mi się głównym powodem dla popularności JSF. Wierzę, że w Polsce już tą kwestię mamy za sobą ;-)

20 lutego 2008

Sesja i przekierowanie żądania w Wicket

4 komentarzy
Czytanie książek informatycznych zaczyna ponownie sprawiać mi przyjemność. Kilkakrotne czytanie tego samego rozdziału i wertowanie kartek w tą i z powrotem w poszukiwaniu odpowiedzi tylko mnie w tym utrwala. Zauważyłem, że lektura Pro Wicket wydawnictwa Manning, jakkolwiek dotyczy wcześniejszych wersji Apache Wicket, to mimo wszystko napisana jest bardzo przyzwoicie. Gdzie tam przyzwoicie?! Czytam rozdział 3 Developing a Simple Application już bodajże z 10 raz i zawsze coś ciekawego odnajduję. Autor książki - Karthik Gurumurthy - wykonał świetną robotę.

Wciąż nie mogę przestać porównywać Wicketa z JSF. Z jednej strony podoba mi się zwięzłość kontrolek JSF a z drugiej przyciąga prostota Wicketa, która objawia się możliwością tworzenia stron w Javie. Tak, nie jest to najprzyjemniejsze, biorąc pod uwagę, co możnaby jeszcze uprościć w tej kwestii patrząc na GWT, ale to, co oferuje Wicket, zaczyna mnie przyciągać ku niemu coraz silniej. JSF to standard, część Korporacyjnej Javy, więc nie sposób go nie znać, ale Wicket rulez.

Od kilku dni "trawię" obsługę sesji w Wicket. Niby nic nadzwyczajnego, a jednak żadne ze znanych mi szkieletów aplikacyjnych do tej kwestii nie podeszło w ten sposób (przypominam, że nie znam Tapestry, a o nim się pisze jako o najbliższym krewnym). Na czym polega fenomen obsługi sesji w Wicket? Na silnym typowaniu obiektów przechowywanych w sesji bez bezpośredniego dostępu do obiektu javax.servlet.http.HttpSession znanego każdemu tworzącemu aplikacje webowe oparte o JSP i serwlety. To tak, jakby dodać typy generyczne do HttpSession.
package pl.jaceklaskowski.wicket;

import org.apache.wicket.Request;
import org.apache.wicket.protocol.http.WebSession;

import pl.jaceklaskowski.wicket.entities.Osoba;

public class WicketDemoSession extends WebSession {

private static final long serialVersionUID = 1L;

private Osoba osoba;

public WicketDemoSession(Request request) {
super(request);
}

public Osoba getOsoba() {
return osoba;
}

public void setOsoba(Osoba osoba) {
this.osoba = osoba;
}

}
pl.jaceklaskowski.wicket.WicketDemoSession to typ reprezentujący sesję w mojej aplikacji opartej o Wicket. Klasa koniecznie musi rozszerzać typ org.apache.wicket.protocol.http.WebSession. Zalecane jest udostępnienie konstruktora z pojedyńczym parametrem reprezentującym bieżące żądanie (typ org.apache.wicket.Request) a pozostałe "rzeczy" są już nasze. Skoro chciałem przechowywać egzemplarz typu Osoba w sesji udostępniłem metodę zapisu i odczytu dla tego typu. I tyle! Chcemy przechowywać więcej w sesji, dodajemy kolejne atrybuty do własnej klasy reprezentującej sesję.

Aktywacja naszej sesji polega na nadpisaniu metody public Session newSession(Request request, Response response) w klasie aplikacji.
package pl.jaceklaskowski.wicket;

...

public class WicketDemoApplication extends WebApplication {

...

@Override
public Session newSession(Request request, Response response) {
return new WicketDemoSession(request);
}
}
I tyle. Użycie sesji to skorzystanie z pomocy org.apache.wicket.Component.getSession(), która sprowadza się do wywołania org.apache.wicket.Session.get(), która z kolei pobiera aktualną sesję z bieżącego wątku (robiąc jeszcze poboczne sprawy). Możliwości dostania się do sesji jest więc kilka, a zwracany obiekt jest "naszego" typu.
package pl.jaceklaskowski.wicket;

import org.apache.log4j.Logger;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.RequiredTextField;
import org.apache.wicket.model.CompoundPropertyModel;

import pl.jaceklaskowski.wicket.entities.Osoba;

public class PrzedstawSie extends WebPage {

private static final long serialVersionUID = 1L;

private transient Logger logger = Logger.getLogger(PrzedstawSie.class.getName());

public PrzedstawSie(final PageParameters params) {
CompoundPropertyModel model = new CompoundPropertyModel(new Osoba());
Form loginForm = new Form("dane", model) {
private static final long serialVersionUID = 1L;

protected void onSubmit() {
Osoba osoba = (Osoba) getModel().getObject();
logger.info(osoba);
((WicketDemoSession) getSession()).setOsoba(osoba);
if (!continueToOriginalDestination()) {
setResponsePage(new Powitanie(getModelObjectAsString()));
}
}
};
RequiredTextField imie = new RequiredTextField("login");
loginForm.add(imie);
add(loginForm);
}
}
Na uwagę zasługuje linia ((WicketDemoSession) getSession()).setOsoba(osoba);, gdzie pobieram sesję i ustawiam w niej egzemplarz typu Osoba. Przy okazji tworzenia tej nowej strony w aplikacji musiałem użyć słowa kluczowego transient, które było moim pierwszym jego użyciem. To też zaliczam jako plus dla Wicketa.

Baczne oko zauważy użycie metody public boolean org.apache.wicket.Component.continueToOriginalDestination(), która zwróci true, jeśli przerwaliśmy wcześniej obsługę żądania (przepływ) za pomocą org.apache.wicket.RestartResponseAtInterceptPageException. Jego zgłoszenie powoduje przerwanie obsługi bieżącego żądania i przekierowanie do wskazanej w konstruktorze wyjątku strony. Najbardziej naturalne użycie tego rodzaju przerwania to obsługa uwierzytelnienia użytkownika.
package pl.jaceklaskowski.wicket;

import java.util.Arrays;

import org.apache.log4j.Logger;
import org.apache.wicket.PageParameters;
import org.apache.wicket.RestartResponseAtInterceptPageException;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.RequiredTextField;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.CompoundPropertyModel;

import pl.jaceklaskowski.wicket.entities.Osoba;

public class DaneOsobowe extends WebPage {

private static final long serialVersionUID = 1L;

private transient Logger logger = Logger.getLogger(DaneOsobowe.class.getName());

public DaneOsobowe(PageParameters params) {
// pobranie danych z sesji
WicketDemoSession session = (WicketDemoSession) getSession();
Osoba osoba = session.getOsoba();
if (osoba == null) {
throw new RestartResponseAtInterceptPageException(PrzedstawSie.class);
}
logger.info("Dane osoby: " + osoba);
CompoundPropertyModel model = new CompoundPropertyModel(osoba);
Form loginForm = new Form("daneOsobowe", model) {
private static final long serialVersionUID = 1L;

protected void onSubmit()
{
// TODO: miejsce dla JPA
Osoba osoba = (Osoba) getModel().getObject();
logger.info(osoba);
((WicketDemoSession) getSession()).setOsoba(osoba);
setResponsePage(new Powitanie(getModelObjectAsString()));
}
};
TextField imie = new TextField("imie");
// określenie pola obowiązkowego
imie.setRequired(true);
loginForm.add(imie);
// pomocnicza klasa wykonująca setRequired(true)
TextField nazwisko = new RequiredTextField("nazwisko");
loginForm.add(nazwisko);
TextField login = new RequiredTextField("login");
login.setRequired(true);
loginForm.add(login);
// lista rozwijalna z danymi z Osoba.getMiejscowosci (model formularza to Osoba)
DropDownChoice choice = new DropDownChoice("miejscowosc",
Arrays.asList(new String[] { "Warszawa", "Krak\u00f3w",
"Wroc\u0142aw", "Pozna\u0144", "Szczecin", "Gda\u0144sk" })) {

private static final long serialVersionUID = 1L;

// wyślij wybór z listy po zmianie wyboru do serwera
@Override
protected boolean wantOnSelectionChangedNotifications() {
return true;
}

protected void onSelectionChanged(final Object newSelection)
{
System.out.println("Miejscowosc: " + newSelection);
}
};
choice.setRequired(true);
loginForm.add(choice);
add(loginForm);
// panel komunikatów
add(new FeedbackPanel("komunikaty"));
}
}
Jeśli wywołanie strony DaneOsobowe nie będzie związane z egzemplarzem Osoba w sesji nastąpi przerwanie do klasy-strony PrzedstawSie, w której po pomyślnym zatwierdzeniu formularza umieszczam wymagany obiekt i wykonanie wspomnianej metody continueToOriginalDestination() spowoduje powrót do wykonania poprzednio przerwanej strony - powraca do przerwanego wątku. Jeśli wywołana zostanie strona PrzedstawSie bezpośrednio, metoda continueToOriginalDestination() zwróci false i nastąpi przekierowanie do strony Powitanie. Proste, nieprawdaż?