31 października 2007

Sun Certified Business Component Developer Java EE 5 (SCBCD) zdany!

8 komentarzy
Zdecydowałem się i udało mi się zdać kolejny certyfikat z serii certyfikatów z korporacyjnej javy 5 - Sun Certified Business Component Developer (SCBCD) for the Java Platform, Enterprise Edition 5 (CX-310-091). Najpierw był Sun Certified Java Programmer (SCJP) 5.0 w grudniu 2006 roku, a teraz przyszła pora na SCBCD5. I cieszyłbym się bardziej, gdyby ten wynik nie był tak fatalny! Wręcz prześlizgnąłem się i w zasadzie to miałem wątpliwości, aby w ogóle napisać o tym. Porażka - jedynie 63% przy poziomie 59% na zdanie egzaminu!


Podszedłem do niego na luzie mając w garści przepustkę (ang. voucher), który upoważniał mnie od podejścia powtórnego, w razie gdyby nie powiodło mi się przy pierwszym. Rozpocząłem o godzinie 10:15 na Zielnej w budynku PASTy - numer lokalizacji: PL41 (aby nie było reklamy). W metrze postanowiłem dokończyć czytanie rozdziału specyfikacji EJB 3.0 o transakcjach Support for Transactions. Wiedziałem, że właśnie transakcje, bezpieczeństwo i wyjątki to moja pięta achillesowa. Niespodziewałem się jednak, że tak słabo wypadnę przy JPA (sekcja Java Persistence API Entities) - 42% oraz przy ziarnach sesyjnych (sekcja EJB 3.0 Session Bean Component Contract & Lifecycle) - 42%. Nie ukrywam, że spodziewałem się lepszego wyniku. Wyszedłem wręcz załamany ;-)

Wyprzedzając pytania napiszę, że uczyłem się głównie ze specyfikacji EJB 3.0, próbowałem się z różnymi pytaniami na grupach i forach dyskusyjnych, przeglądałem dokumentację i przykłady do serwerów aplikacyjnych, głównie Glassfish, Geronimo, ale również ciekawą i pełną dokumentację znalazłem na stronach dokumentacji Apache OpenJPA oraz na stronach Getting Started with EJB 3.0. Podsumowaniem wiedzy była lektura książki Enterprise JavaBeans 3.0 autorstwa Billa Burke oraz Richarda Monsona-Haefela z OReilly (której recenzja czeka na opublikowanie) i przejrzałem pobieżnie EJB 3 in Action wydawnictwa Manning. Ciekawym miejscem wydaje się również forum EJB Certification (SCBCD) na JavaRanch, ale przynaję, że za mało tam zaglądałem.

Jakie były pytania niestety nie mogę napisać, ale należy przygotować się w dużej mierze myśląc o transakcjach oraz bezpieczeństwie każdorazowo, kiedy jakikolwiek temat EJB 3.0 jest poruszany. Są to zagadnienia zazwyczaj traktowane po macoszemu, a przecież to one są głównym powodem zastosowania EJB 3.0 w aplikacjach. Nie można zapominać o konfiguracji ziaren poprzez deskryptory - ejb-jar.xml, persistence.xml, czy orm.xml i wszystkie inne, do których się odwołują.

Jednymi z pytań, nad którym siedziałem i nadal nie jestem odpowiedzi dotyczyły momentu utworzenia kontekstu trwałego. Jeśli kontekst trwały zarządzany jest przez aplikację, to według mnie jego istnienie rozpoczyna się w momencie utworzenia, w przeciwieństwie do kontekstu zarządzanego przez kontener, gdzie jego istnienie rozpoczyna się od momentu rozpoczęcia transakcji. Czy tak jest na pewno będę musiał sprawdzić. Wiele z pytań było wyjątkowo podchwytliwych i niskopoziomowych, i prawdopodobnie nie przyjdzie mi z tej wyrafinowanej wiedzy intensywnie korzystać, ale kto wie, co mnie czeka w przyszłości - może przyda się szybciej niż mi się wydaje. Polecam podejście do egzaminu SCBCD5, bo porządkuje wiedzę z EJB3, a i wiele ciekawostek można dowiedzieć się właśnie podczas jego trwania ;-) Nie pamiętam, co to było, ale w pewnym momencie prawie krzyknąłem widząc kawałek kodu, w którym dosyć interesująco była wykorzystana funkcjonalność EJB3 (podobnie było podczas czytania książki Billa Burke'a Enterprise JavaBeans 3.0, gdzie zaprezentował ciekawe użycie metod przechwytujących - interceptorów do realizacji specjalizowanych adnotacji). Przypomniałem sobie właśnie, że było jeszcze jedno pytanie, które mnie wykończyło - mechanizm wstrzeliwania zależności i równość egzemplarzy wstrzelonych ziaren. Czy równość (wyznaczana metodą equals) zajdzie dla ziaren sesyjnych przekazanych za pomocą DI? Czy jest różnica między referencjami do ziarna stanowego i bezstanowego? To również będę musiał sprawdzić. W ogóle muszę przysiąść nad tymi transakcjami, wyjątkami i bezpieczeństwem. Reszta wierzę, że uzupełni się w międzyczasie.

Życzę wszystkim podchodzącym do SCBCD5 jak najlepszych not, na pewno lepszych niż takie mierne 63%. Szczęśliwie, że to nie ode mnie zależy jaki poziom jest zdający, bo ustawiłbym go zdecydowanie wyżej, powiedzmy na 75% ;-)

30 października 2007

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

3 komentarzy
Warszawska Grupa Użytkowników Technologii Java (Warszawa JUG) zaprasza na XVIII spotkanie, które odbędzie się we wtorek 30.10.2007 o godzinie 18:00 w sali 4420 na Wydziale MIMUW przy ul. Banacha 2 w Warszawie.

Temat prezentacji: JSF - Pytania i Odpowiedzi przez Przykłady
Prowadzący: Wiktor Gworek, Jacek Laskowski et al.

JavaServer Faces (JSF) nie jest nową technologią Java EE, ale dopiero od wersji Java EE 5 weszła jako obowiązkowy element specyfikacji. JSF jest specyfikacją modularnego interfejsu użytkownika dla aplikacji internetowych. Powstał, aby przybliżyć możliwości tworzenia interfejsu w sposób zbliżony do tworzenia aplikacji desktopowych.

Spotkanie będzie prowadzone w formie warsztatu. Pierwsze 30 minut będzie wprowadzeniem w temat z demonstracją przykładowych aplikacji, podczas gdy kolejne 30 minut będzie już dotykało bardziej zaawansowanych funkcjonalności dostarczanych przez JSF. Nie przewiduje się żadnych slajdów, a jedynie otwarte środowisko programisty (IDE) i tworzenie aplikacji na żywo z objaśnianiem co ciekawszych elementów JSF.

A dlaczego warto przybyć na spotkanie? Dla dopiero rozpoczynających poznawanie JSF jest to idealna okazja na podniesienie wiedzy o JSF w niezwykle efektywny sposób - tworząc aplikację JSF w gronie z innymi, już zaznajomionymi z tematem. Dla zaawansowanych będzie to dobry trening przed egzaminem SCJWD5 i zorientowanie się w problemach nowicjuszy, które często prowokują do przemyśleń i uzupełnienia wiedzy. Dla wszystkich innych będzie możliwością uczestniczenia w ciekawym doświadczeniu polegającym na prowadzeniu prezentacji bez slajdów. Poza elementami JSF będą wykorzystane różne pomocnicze narzędzia wspierające tworzenie aplikacji JSF, więc każdy powinien znaleźć coś dla siebie. Wszelkie pytania dotyczące JSF mile widziane. W zasadzie, cała prezentacja to zbiór pytań i odpowiedzi w postaci (najczęściej) działających aplikacji JSF.

Prezentację poprowadzą Wiktor Gworek, Jacek Laskowski oraz...Wy, uczestnicy.

Wiktor Gworek jest studentem V roku na Wydziale MIM UW i nowych technologii. Wiktor uwielbia ładne, radosne programowanie, kunsztowną architekturę oprogramowania oraz czytanie RSSów. Z Javą związany jest od dwóch lat, jednak stroni on od monogamii programistycznej. Prowadzi blog Mocna Kawa.

Jacek Laskowski zajmuje się Korporacyjną Javą od pierwszego dnia jej opublikowania. Jest członkiem zespołów rozwojowych Apache Geronimo, Apache OpenEJB, Apache ServiceMix, Apache XBean oraz Apache ActiveMQ. Zajmuje się rozpoznawaniem technologii upraszczających tworzenie aplikacji korporacyjnych z Java EE 5, a swoje spostrzeżenia publikuje w Notatniku Projektanta Java EE oraz na swoim Notatniku-Wiki. Jest założycielem i liderem Warszawskiej Grupy Użytkowników Javy (Warszawa JUG). Jacek jest członkiem zespołu NetBeans 6.0 Community Acceptance Test (poprzednio w NetCAT 5.0 oraz 5.5). Ideę dzielenia się wiedzą wprowadza w życie poprzez wystąpienia na konferencjach krajowych i zagranicznych. Służbowo związany z firmą IBM jako konsultant oprogramowania.

Planowany czas prezentacji to 1,5 godziny. Na zakończenie (około 15 minut) planowana jest dyskusja dotycząca organizacji konferencji Warsjava organizowanej przez Warszawa JUG w dniu 17.11.2007.

Wstęp wolny!

Zapraszam w imieniu Warszawa JUG!

Mechanizm rozwiązywania zmiennych w JSF - variable-resolver

1 komentarzy
W trakcie pracy z rozszerzeniem EJB 3.0 dla IBM WebSphere Application Server 6.1 (WAS) natrafiłem na problem z mechanizmem wstrzeliwania zależności w aplikacji JSF 1.1. WAS 6.1 jest serwerem aplikacji wspierającym specyfikację Java EE 1.4 i część specyfikacji Java EE 5 jest realizowana przez tzw. rozszerzenia funkcjonalne (ang. feature packs), które dostarczają wybrane części specyfikacji. Jedną z usług, której brakuje jest usługa wstrzeliwania zależności dla ziaren zarządzanych JSF. Innymi słowy, dla WAS 6.1 ziarna zarządzane JSF nie są w pełni zarządzane i mechanizm wstrzeliwania zależności ich nie obsługuje. Konstrukcje typu @EJB w przypadku ziaren JSF nie są obsługiwane i kończy się zazwyczaj na NullPointerException. W ten sposób natrafiłem na kolejną funkcjonalność JSF - komponenty rozwiązujące zmienne (ang. variable resolvers), dalej zwane komponentami rozwiązującymi. Zaintrygowany? Zajrzyj do artykułu Mechanizm rozwiązywania zmiennych w JSF - variable-resolver. Miłej lektury!

28 października 2007

Aplikacja Java EE 5 z MDB z JPA w trybie JTA i PostgreSQL w Apache Geronimo 2

0 komentarzy
Podczas konferencji Java Developers Day 2007 w Krakowie, na której wystąpiłem z tematem Tworzenie aplikacji Java EE z Apache Geronimo 2 nie udało mi się zaprezentować wszystkich, 3-ech przykładów. Padło na najbardziej interesujący technologicznie przykład Ziarno MDB z JPA w trybie JTA. Nadrabiając straty udało mi się opisać to, co miało zostać powiedziane i stworzyłem kolejny artykuł przybliżający serwer Apache Geronimo wraz z ziarnem MDB i encji JPA w trybie JTA - Aplikacja Java EE 5 z MDB z JPA w trybie JTA i PostgreSQL w Apache Geronimo 2. Przykład prezentuje możliwości Geronimo w zakresie obsługi transakcji JTA w warstwie EJB 3.0, na poziomie MDB i JPA. W artykule zawarłem kilka informacji specyficznych dla Geronimo, co powinno przybliżyć sposób jego działania i być może sprowokować do dalszych prób technologicznych. Uruchomienie klienta zdalnego za pomocą wtyczki exec:exec mavena może być również ciekawe. Wszelkie komentarze o merytorycznej stronie artykułu mile widziane, a wskazówki do dalszych eksperymentów nawet bardziej. Miłej lektury!

p.s. Grupa Warszawa JUG organizuje warsztaty javowe - Warsjava - na których będzie dużo takich technologicznych perełek. Zapraszam zainteresowanych do udziału, czy to jako prelegent, czy uczestnik. Wstęp wolny, a dobrej, technicznej zabawy, będzie co nie miara!

27 października 2007

Relacja z Java Developers Day 2007 w Krakowie

12 komentarzy
Już rano mnie ścisnęło, kiedy nie było wątpliwości, że tyle materiału (a w tym i przykładów), ile planowałem pokazać na mojej prezentacji Tworzenie aplikacji Java EE 5 z Apache Geronimo 2 podczas krakowskiej konferencji Java Developers Day 2007 jest niemożliwe w 45 minut. Najgorsze było to, że nie wiedziałem, co powinienem wyrzucić, co jest niepotrzebne, aby ostatecznie utrzymać czas. Dodatkowo, aby podnieść rangę owych 45 minut, o 20:00 miałem pociąg powrotny do Warszawy, więc koniec prezentacji - 19:15 - musiał być utrzymany. I był, ale jak można przeczytać w komentarzach, było ciekawie, ale zdecydowanie za krótko. I faktycznie, samemu odczułem brak czasu i mimo, że mogłem zaoszczędzić 10-15 minut, to i tak nie pokazałbym NetBeans IDE 6.0 z Apache Geronimo 2 i przykładu z MDB, JPA i JTA jak i kolejnego z JAX-WS 2.0. Na szczęście, nie traciłem czasu na niepotrzebne uruchamianie oprogramowania (wszystko miałem już uruchomione), jak i budowanie aplikacji, itp. Oprogramowanie działało bez zarzutów, a wierzę, że nie były to trywialne przykłady.

Pierwszy przykład był prezentacją JSF 1.2 na Apache Geronimo (w wykonaniu Apache MyFaces) z pomocą Eclipse IDE 3.3.1 z wtyczką Geronimo. Prosta aplikacja, więc poszło gładko. Geronimo i Eclipse spisali się znakomicie. Przez moment wzbudziłem falę śmiechu, kiedy to z pewną miną poprawnego uruchomienia aplikacji pojawił się wyjątek java.lang.RuntimeException: FacesContext not found.


Nie straciłem jednak rezonu, planując jego wystąpienie świadomie, który wynika nie z niepoprawnego działania Geronimo, ale z wykonania aplikacji JSF bez zainicjowanego kontekstu jsf (brak uruchomienia FacesServlet). Nie byłem nim wcale zdziwiony (ururchomienie aplikacji poprzez adres zawierający /faces/ było rozwiązaniem). Wtedy zapytałem o znajomość JSF i...dopiero teraz faktycznie byłem zdumiony. Na moje pytanie bodajże 3-5 osób podniosło rękę (!) Zrozumiałem wtedy, że to co bawi jednych nie musi innych (ciekawe ile osób wyszło przekonanych o planowanym błędzie). Drugi raz, zdumiała mnie przytłaczająca popularność Eclipse IDE jako środowiska programistycznego (podniesione ręce po pytaniu o użycie NetBeans IDE były ledwie zauważalne, mniej niż użytkowników vi (!)).

Tutaj warto wspomnieć o liczbie uczestników podczas konferencji. Niektórzy mówili o 300 osobach. I to mogło być prawdą. Podczas mojej, kończącej konferencję, prezentacji pobieżne moje szacunki wskazywały na circa 150-200 osób (zdjęcie poniżej było robione na prędce zaraz po zakończeniu mojej prezentacji).


Udało mi się zaprezentować dwa przykłady (z zaplanowanych 3-ech) - Utworzenie aplikacji internetowej z JSF 1.2 oraz Ziarno sterowane komunikatami (MDB) z klientem zdalnym. Oba opisane są w moich artykułach Tworzenie aplikacji z JavaServer Faces, Apache Maven i Apache Geronimo oraz Uruchomienie ziarna MDB w Apache Geronimo. Niezaprezentowany przykład Ziarno MDB z JPA w trybie JTA niedługo zostanie zaprezentowany w postaci artykułu.

A tak ogólnie o konferencji, to możnaby napisać wyłącznie w samych superlatywach. Jedzenie było i to pierwsza klasa (akurat przyjechałem do Krakowa na porę obiadową). Napoje dostępne przez całą konferencję. Sala, nagłośnienie, asysta na 6+. Brakowało jedynie równoległych paneli, co mogłoby wpłynąć na zwiększenie czasu na prezentacje z 45 minut do powiedzmy 1,5 godziny (oczywiście mogłoby to zależeć od samych prelegentów i dla mnie 1,5 godziny byłoby odpowiednie). Wszystkie prezentacje były nagrywane i nie mogę doczekać się filmu z mojej (planuję usprawnienia i materiał filmowy będzie idealny, aby podnieść swój warsztat prezenterski).

Nie mógłbym nie wspomnieć o wizycie Billa Burke z Red Hat/JBoss. Tym bardziej mnie wizyta ucieszyła, że właśnie niedawno przeczytałem jego książkę Enterprise JavaBeans 3.0, która napisana jest bardzo ciekawie i z polotem. Widać, że autor - Bill Burke - wiedział o czym pisze, a jeśli by dodać do tego, że Bill jest weteranem Java EE i współautorem JBoss AS, to wiadomo, skąd to rozeznanie tematu. Oczywiście, nie każdy inżynier to dobry autor książki, jednakże w jego przypadku połączenie tych dwóch ról wypadło znakomicie. Gratulacje Bill za wspaniałą książkę! W Bibliotece Warszawskiego JUGa mamy tę pozycję, więc zabrałem ją, aby prosić o dedykację Billa. Dziękuję Tomkowi (szimano) za pomoc w realizacji planu - książka została podpisana przez Billa (i może w ten sposób zaintryguje innych grupowiczów do jej lektury i recenzji).

Inne spojrzenie na konferencję JDD07 można znaleźć na stronie Inicjatywy IT w Krakowie - Rozpoczął się Java Developers Day (koszulki nie umknęły autorowi ;-)).

Zapraszam na warsztaty javowe Warsjava 2007 organizowane przez grupę Warszawa JUG, na której z pewnością nie zabraknie wiele ciekaw(sz)ych prezentacji. Będzie technicznie do bólu. To już niedługo, bo 17 listopada 2007 w Warszawie. Wstęp wolny!

Prezentacja z konferencji Java Developers Day 2007 w Krakowie: Tworzenie aplikacji Java EE 5 z Apache Geronimo 2.

22 października 2007

JDD2007 w Krakowie już w najbliższy piątek - jakie są Twoje oczekiwania?

7 komentarzy
W nadchodzący piątek, 26 października 2007 w Krakowie podczas Java Developers Days 2007 będę przez 45 minut prezentował możliwości Apache Geronimo 2 tworząc aplikację Java EE 5. Z każdym dniem mam inne pomysły, jak poprowadzić prezentację tak, aby była ona z pożytkiem dla większości słuchaczy. Nie chciałbym, aby na zakończenie konferencji dobić wytrwałych i zmęczonych całym dniem nudną i banalną prezentacją z 5-10 slajdami, a może nawet i zniechęcić do Geronimo (nie wspominając o moich kolejnych występach). Pomyślałem, że dobrze byłoby wypytać potencjalnych uczestników, czego oczekują od prezentacji, na co się nastawiają.

I właśnie teraz jest ten moment. Wy mówicie, czego oczekujecie, a ja to realizuję. Tematów do prezentacji jest wiele, jednakże czasu bardzo mało, a dodając do tego późną porę możecie być już kompletnie zmęczeni. Stąd prośba o wyrażenie swoich oczekiwań w stosunku do prezentacji, aby ostateczny produkt - prezentacja Tworzenie aplikacji Java EE z Apache Geronimo 2 była wartościowa. Postaram się odpowiedzieć na wszystkie prośby, być może nie treściwie i w pełni, ale próba na pewno będzie. Chciałbym pokazać co oferuje Eclipse IDE 3.3, RedHat Developer Studio 1.0.0.beta2, NetBeans IDE 6.0 oraz IntelliJ IDEA 7.0 w stosunku do Geronimo 2, jednakże pytanie, czy wszystkie środowiska i w końcu jaką aplikację powinienem stworzyć i uruchomić, aby zaprezentować możliwości Apache Geronimo 2 we wspieraniu Korporacyjnej Javy 5. Na pewno będzie JSF w wykonaniu Apache MyFaces i JPA z Apache OpenJPA, i EJB3 z Apache OpenEJB i JMS z Apache ActiveMQ i Jetty 6. Na co jeszcze zwrócić uwagę?

21 października 2007

Uruchomienie ziarna MDB w Apache Geronimo

0 komentarzy
Podczas rozpoznawania EJB3 w kontekście ziaren MDB (ang. message-driven bean) postanowiłem zbadać temat korzystając z Apache Geronimo 2. Problemem okazało się nie utworzenie i uruchomienie ziarna, ale wysłanie komunikatów do kolejki, z którą ziarno było związane z klienta, który nie byłby aplikacją internetową. Na bazie przykładu opisanego w dokumentacji Apache Geronimo - JMS and MDB sample application stworzyłem artykuł Uruchomienie ziarna MDB w Apache Geronimo wraz z przedstawieniem klienta zdalnego, który wysyła komunikaty przechwytywane następnie przez ziarno (w przeciwieństwie do aplikacji internetowej, która została opisana w dokumentacji). Miłej lektury!

16 października 2007

EJB 3.0 - Rozdział 18 Usługa Budzika (Timer Service)

7 komentarzy
Niedługo mój wymarzony dzień egzaminu EJB 3.0, do którego przygotowuję się od wieków i tak przeciągam to podejście, i przeciągam. Zawsze coś - a to jeszcze książkę przeczytać (nota bene czeka mnie opublikowanie recenzji książki Enterprise JavaBeans 3.0 Billa Burke & Richarda Monson-Haefela), a to jeszcze artykuł, a to rozdział w specyfikacji EJB 3.0 i dodając do tego pytania na różnych grupach dyskusyjnych nie ma nawet czasu pomyśleć o egzaminie ;-) Najwyraźniej doskwiera mi problem z określeniem momentu, w którym należałoby powiedzieć sobie Dość! (coś na wzór powiedzenia, które ostatnio pojawiło mi się w GMailu autorstwa Oswalda Chambersa The whole point of getting things done is knowing what to leave undone.).

Do egzaminu jeszcze chwila, więc zainspirowany pytaniem na pl.comp.lang.java postanowiłem zapoznać się z tematem poprzez lekturę specyfikacji EJB 3.0 - EJB Core Contracts and Requirements. Dzisiaj padło na rozdział 18 Timer Service. Podobnie jak miało to miejsce w przypadku relacji z lektury specyfikacji JPA, dzisiaj przedstawię EJB3.

Moja propozycja tłumaczenia timer service to usługa budzika. Komentarze odnośnie trafności (bądź jego braku) mile widziane.

Usługa Budzika (powiadamiania) w EJB 3.0 (ang. EJB 3.0 Timer Service) jest transakcyjną usługą dostarczaną przez kontener EJB 3.0, która umożliwia zarejestrowanie ziaren EJB (a w zasadzie ich metod) do uruchomienia o zadanym czasie, po upływie zadanego czasu (budzik jednokrotnego wzbudzenia) lub w określonych interwałach czasowych (budzik wielokrotnego wzbudzenia). Dostęp do usługi następuje poprzez mechanizm wstrzeliwania zależności (DI), interfejs javax.ejb.EJBContext (metoda getTimerService()) lub odszukanie w drzewie JNDI.

Specyfikacja podkreśla fakt, że usługa budzika jest usługą dostarczającą powiadamiania do modelowania długotrwałych procesów biznesowych, a nie czasu rzeczywistego. Jakkolwiek przedziały czasowe można wyrazić korzystając z milisekund (ze względu na wykorzystanie API z Java SE), to zakłada się użycie usługi w przedziałach godzinnych, dziennych lub dłuższych.

18.2 Usługa Budzika oczyma dostawcy ziaren EJB


Usługa Budzika dostarcza metody pozwalające na utworzenie lub usunięcie budzika, jak również pozyskania listy budzików związanych z danym ziarnem.

Budzik jest ustawiony na generowanie zdarzeń czasowych (związanych z upływającym czasem). Klasa ziarna EJB, które używa usługi budzika musi dostarczać metodę zwrotną uruchamianą podczas wystąpienia zdarzenia czasowego (czasowa metoda zwrotna). Metoda czasowa wskazana jest przez adnotację @Timeout, element timeout-method w deskryptorze rozmieszczenia, lub będącą metodą ejbTimeout interfejsu javax.ejb.TimedObject, jeśli ziarno go implementuje. Budziki mogą być tworzone dla bezstanowych ziaren sesyjnych (ziarna SLSB), ziaren sterowanych komunikatami (ziarna MDB) oraz ziaren encyjnych, jeśli te spełniają wymagania specyfikacji EJB 2.1 (ukłony, dla wszystkich tych, którzy zechcieli wspierać rozwiązania oparte o EJB 2.1 i dowiadują się o usprawnieniach Korporacyjnej Javy 5 bez możliwości ich wykorzystania - nie jest lekko ;-)). Budziki nie mogą być tworzone dla stanowych ziaren sesyjnych oraz encji JPA.

Wzbudzenie budzika dla ziaren SLSB czy MDB następuje na dowolnym egzemplarzu w stanie oczekiwania (ang. pooled state).

Podczas upłynięcia czasu budzika, kontener wywołuje czasową metodę zwrotną ziarna, z którym jest związany. Budzik może zostać zatrzymany przez ziarno zanim upłynie czas aktywności budzika. Jeśli budzik zostanie zatrzymany, metoda czasowa nie zostanie wywołana (chociaż zgodnie z uwagą w specyfikacji może się zdarzyć nadmiarowe wywołanie - kolejne wskazanie o biznesowej roli usługi budzika a nie do modelowania systemu czasu rzeczywistego). Budzik zatrzymywany jest metodą Timer.cancel().

Wywołania metod tworzących i zatrzymujących budzik oraz metod czasowych są zazwyczaj wykonywane w ramach transakcji. Podczas wycofywania/zatwierdzania transakcji, operacja budzika jest odpowiednio wycofywana/zatwierdzana. Metoda czasowa jest zazwyczaj opatrzona atrybutem transakcyjnym REQUIRED lub REQUIRES_NEW. Wycofanie transakcji oznacza ponowne wykonanie metody czasowej.

Budziki są trwałe. Ich stan jest utrzymywany bez względu na wystąpienie awarii serwera czy cykli pasywacji/aktywacji oraz ładowania/zapisywania ziaren, z którymi są związane.

Interfejs javax.ejb.TimerService składa się z metod do tworzenia budzików (createTimer) z różnymi kombinacjami parametrów określających czas aktywacji oraz metoda zwracająca kolekcję aktywnych budzików getTimers().

Ziarno może przekazać informację specyficzną dla klienta wyłącznie podczas utworzenia budzika (parametr info typu java.io.Serializable w metodach createTimer). Informacja musi być zgodna z mechanizmem serializacji w Javie.

Ziarno może dostarczać co najwyżej jedną metodę czasową (w ramach całego drzewa dziedziczenia). Jeśli ziarno implementuje interfejs javax.ejb.TimedObject, wtedy adnotacja @Timeout lub element timeout-method musi wyłącznie wskazywać na metodę ejbTimeout() z interfejsu.

Dowolna metoda oznaczona adnotacją @Timeout musi mieć następującą sygnaturę:
  void <dowolna-nazwa-metody>(javax.ejb.Timer timer)
Metoda może być dowolnej widoczności: public, private, protected lub domyślnej (package protected) i nie może być final lub static.

Metoda czasowa nie może zgłaszać wyjątków aplikacyjnych.

Podczas wzbudzenia budzika następuje wywołanie metody czasowej, w której można wywołać metodę Timer.getInfo(), która zwróci informacje specyficzne dla budzika przekazane w metodzie createTimer() podczas jego tworzenia.

Na stronie 484 specyfikacja ponownie podkreśla możliwość wystąpienia wielu uruchomień metody czasowej bez jakichkolwiek założeń o moment między wywołaniami innych metod ziarna, czy ich ilość i kolejność.

Metoda czasowa może wykonywać te same operacje jak metody biznesowe ziarna czy metody nasłuchujące MDB.

Wywołanie metody czasowej nie jest związane z kontekstem bezpieczeństwa. Metoda czasowa jest wewnętrzną metodą nie udostępnioną klientom, więc wywołanie EJBContext.getCallerPrincipal() zwróci użytkownika nieuwierzytelnionego tak, jak reprezentowany jest przez serwer.

W przypadku budzika jednokrotnego wzbudzenia, kontener usuwa budzik po poprawnym zakończeniu wykonania metody czasowej, tj. podczas zatwierdzenia transakcji, z którą był związany. Jeśli ziarno wywoła metodę na budziku po zakończeniu metody czasowej zgłaszany jest wyjątek javax.ejb.NoSuchObjectLocalException.

Interfejs javax.ejb.Timer pozwala na zatrzymanie (metoda cancel()) budzika, pozyskanie o nim informacji (metody getTimeRemaining() oraz getNextTimeout()) jak również informacji klienckich specyficznych dla niego (metoda getInfo()) i uchwyt (getHandle()), który umożliwia utrwalenie budzika w wybranym miejscu (baza danych, dysk, itp.). Budziki są obiektami lokalnymi i nie mogą być udostępniane przez biznesowy interfejs zdalny czy interfejs usługi sieciowej.

Porównanie egzemplarzy budzików może być wykonane wyłącznie przy pomocy metody Timer.equals(Object obj).

Jeśli klasa ziarna ma klasy nadrzędne, metoda czasowa może być zadeklarowana przez/udostępniana w klasie ziarna lub jego nadklas.

18.4 Wymagania stawiane kontenerowi EJB (serwerowi aplikacji Java EE)

Egzemplarze budzików nie mogą być serializowalne.

Kontener zobowiązany jest dostarczyć implementację budzika, która będzie do użycia przez cały jego cykl rozwojowy.

Kontener zobowiązany jest dostarczyć właściwą implementację metod Timer equals(Object obj) oraz hashCode().

Jeśli wykorzystywane jest określanie zasięgu transakcji przez kontener (CMTD) i korzysta się z atrybutów REQUIRED lub REQUIRES_NEW (Required lub RequiresNew w deskryptorze rozmieszczenia), kontener musi rozpocząć nową transakcję zanim nastąpi wywołanie metody czasowej. Jeśli transakcja zakończy się niepowodzeniem lub zostanie wycofana, kontener musi ponownie uruchomić metodę czasową co najmniej raz.

Jeśli dostawca ziarna wywoła metodę setRollbackOnly() w ramach metody czasowej, kontener musi wycofać transakcję oraz ponownie wykonać metodę.

Budziki są trwałe. Podczas awari serwera i jego ponownego uruchomienia, budzik pojedyńczego wzbudzenia, który powinien zostać wzbudzony podczas przerwy, zostanie uruchomiony podczas uruchomienia serwera. Budzik wielokrotnego wzbudzenia zostanie wywołany co najmniej raz w takim przypadku.

Usunięcie ziarna powoduje usunięcie jego budzików.

Na zakończenie przykład ziarna bezstanowego, który dostarcza metodę czasową oznaczoną adnotacją @Timeout.
 package pl.jaceklaskowski.timerservice;

import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerService;
import javax.jws.WebService;

@Stateless
@WebService
public class TimedSessionBean implements TimedSessionRemote {

@Resource
TimerService timerService;

public void utworzBudzik(long duration) {
long iloscMinut = duration * 1000 * 60;
wypiszKomunikat("Tworzę budzik o czasie wykonania za " + duration + " minut(y).");
timerService.createTimer(iloscMinut, null);
}

@Timeout
private void metodaCzasowa(Timer timer) {
wypiszKomunikat("Metoda czasowa została wzbudzona");
}

private void wypiszKomunikat(String komunikat) {
Locale polski = new Locale("pl");
Date teraz = Calendar.getInstance(polski).getTime();
DateFormat formatDaty = DateFormat.getTimeInstance(DateFormat.LONG, polski);
System.out.println(formatDaty.format(teraz) + ": " + komunikat);
}
}
Uruchomienie metody utworzBudzik z parametrem 2 (poprzez interfejs usługi sieciowej) spowodowało wyświetlenie następującego komunikatu w dzienniku zdarzeń serwera Glassfish:
 WSTX-COMMON-2007: Map CMT EJB web method 'pl.jaceklaskowski.timerservice.TimedSessionBean'.'utworzBudzik' 
with effective transaction attribute of 'REQUIRED' to wsdl bounded operation
'{http://timerservice.jaceklaskowski.pl/}TimedSessionBeanPort':'utworzBudzik'
with WS-AT policy assertion(s) 'TimedSessionBeanPortBinding_utworzBudzik_WSAT_Policy'
...
08:43:54 CEST: Tworzę budzik o czasie wykonania za 2 minut(y).
08:45:54 CEST: Metoda czasowa została wzbudzona

15 października 2007

Konfiguracja aplikacji JSF ze stronami z i bez Facelets

0 komentarzy
Przez długi czas pytanie Rafała JSF || facelets na grupie pl.comp.lang.java nie dawało mi spokoju. Pytanie dotyczyło migracji aplikacji JSF do Facelets, w taki sposób, że aktualna funkcjonalność pozostałaby bez zmian (bez Facelets), podczas gdy nowe strony już korzystałyby z facelets. Pomysł polega na równoległym utrzymywaniu stron bez facelets i tworzeniu nowych z facelets. Jako, że gro czasu zajmowało mi rozpoznawanie EJB3 zostawiłem rozwiązanie problemu na później. W końcu, kiedy skończyłem prezentację konwerterów w JSF pomyślałem, że w tym tonie zabiorę się za facelets i to właśnie od tego pytania.

Kiedy już miałem odpowiedzieć, że takie rozdzielenie się nie da, aż mnie natchnęło i zacząłem kombinować z różnymi konfiguracjami mapowania servletu FacesServlet. Niestety trochę to trwało, aż natrafiłem na parametr facelets.VIEW_MAPPINGS, który wskazał mi właściwe rozwiązanie (w zasadzie to znalazłem odpowiedź w How do I use Facelets and JSP in the same application?).

Uruchomienie facelets wymaga definicji komponentu zarządzającego widokiem w faces-config.xml.

<?xml version='1.0' encoding='UTF-8'?>

<faces-config version="1.2"
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-facesconfig_1_2.xsd">
<application>
<view-handler>
com.sun.facelets.FaceletViewHandler
</view-handler>
</application>
</faces-config>
Tutaj wszystko "po staremu". Klucz do rozwiązania leży w konfiguracji aplikacji internetowej - 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">
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.jsp</param-value>
</context-param>
<context-param>
<param-name>facelets.VIEW_MAPPINGS</param-name>
<param-value>*.xhtml</param-value>
</context-param>
<context-param>
<param-name>facelets.DEVELOPMENT</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.verifyObjects</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.validateXml</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
</web-app>
Przy takiej konfiguracji wywołanie strony pod adresem /faces/welcomeJSF.jsp spowoduje wykonanie jej bez udziału facelets, który w prawdzie będzie wyzwalany, ale będzie akceptował wyłącznie strony zakończone rozszerzeniem .xhtml (parametr facelets.VIEW_MAPPINGS), np. /faces/welcomeJSF.xhtml. Inne rozszerzenia są poza zainteresowaniem facelets. Istotne jest również związanie FacesServlet poprzez mapowanie ścieżki (url-pattern ustawione na /faces/*) zamiast mapowania po rozszerzeniu.

Przykładowy projekt NetBeans IDE aplikacji internetowej korzystającej z "czystego" JSF oraz facelets dostępny jest jako jsf-zibezfacelets-webapp.zip.

14 października 2007

Konwertery w JavaServer Faces część 6

2 komentarzy
Podczas mojej prezentacji mechanizmu konwerterów w JSF korzystałem z pomocy tymczasowej klasy pl.jaceklaskowski.konwerter.TymczasowaBazaDanych, której czas się właśnie kończy. Klasę TymczasowaBazaDanych zastąpię bardziej wartościową usługą dostarczaną przez serwer aplikacji Java EE 5 - Java Persistence (JPA). Wprowadzenie JPA służy usprawnieniu działania konwertera stworzonego w poprzednich częściach z serii Konwertery w JavaServer Faces.

Zanim zacznę warto zauważyć, że do chwili obecnej przykłady można było uruchomić na kontenerze servletów, np. Apache Tomcat, Jetty, czy Resin. Kolejne zmiany będą wymagały pełnego serwera aplikacji Java EE (chociaż znawcy tematu JPA mogliby wskazać na taką konfigurację JPA, któraby tego nie wymagałaby). Poprzednie przykłady uruchamiałem na Glassfish, który jest serwerem aplikacji Java EE 5 i tak pozostanie i tym razem, więc dla mnie nie będzie żadnej zmiany środowiska (wybór podyktowany był wyłącznie wsparciem serwera przez NetBeans IDE 6.0).

W Konwertery w JavaServer Faces część 4 przedstawiłem sposób związania konwertera z kontrolką za pomocą atrybutu converter w h:selectOneMenu. Później, w Konwertery w JavaServer Faces część 5 wykorzystałem możliwość konfiguracji konwertera w pliku faces-config.xml. Wykorzystanie JPA zarządzanego przez serwer aplikacji wymaga, aby powrócić do wykorzystania możliwości atrybutu converter. Powód zostanie wyjaśniony za moment.

Rozpocznę od modyfikacji konwertera pl.jaceklaskowski.konwerter.PracownikConverter.

package pl.jaceklaskowski.konwerter;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.persistence.EntityManager;

public class PracownikConverter implements Converter {

private EntityManager em;

public PracownikConverter(EntityManager em) {
this.em = em;
}

public Object getAsObject(FacesContext context, UIComponent component, String value) {
return em.find(Pracownik.class, Long.valueOf(value));
}

public String getAsString(FacesContext context, UIComponent component, Object value) {
return Long.toString(((Pracownik) value).getNumer());
}
}
Zmiana polega na użyciu zarządcy trwałego JPA i tym samym usunięciu wykorzystania klasy pl.jaceklaskowski.konwerter.TymczasowaBazaDanych jako dostawcy informacji o pracownikach z bazy danych. Możnaby zapytać o cel wprowadzenia konstruktora konwertera z parametrem będącym zarządcą trwałym, skoro w środowisku serwera Java EE 5 można użyć adnotacji @PersistenceContext? Właśnie tu tkwi problem i to, co za chwilę zaprezentuję będzie możliwym obejściem braku obsługi konwerterów przez mechanizm wstrzeliwania zależności (ang. DI - dependency injection). Z niezrozumiałych dla mnie powodów, konwertery zostały wyłączone z obsługi mechanizmu DI, tj. nie są komponentami zarządzanymi z punktu widzenia specyfikacji Java EE 5, a tylko w nich można oczekiwać poprawnego działania adnotacji. Brak wsparcia dla DI dla konwerterów rekompensuję przez wprowadzenie konstruktora, który akceptuje parametr wejściowy będący referencją do egzemplarza typu javax.persistence.EntityManager (który, gdyby serwer Java EE 5 wspierał DI dla konwerterów byłby wstrzelowny automatycznie). Rozwiązanie będzie polegało na przekazaniu zarządcy encji z komponentu, który z funkcjonalności DI może korzystać. Warto więc odpowiedzieć na pytanie, który z elementów aplikacji JSF ma taką możliwość? Oczywiście jest to ziarno zarządzane (zgodnie z jego nazwą). Jeśli do tego dodać, że ziarno może udostępniać właściwość, której wartość może być wykorzystana w atrybutach kontrolek, np. converter, to dalsze kroki powinny być już jasne. Pora na zmiany w aplikacji.

Po pierwsze, aby skorzystać z JPA musimy ustanowić klasę pl.jaceklaskowski.konwerter.Pracownik encją JPA, co pociąga za sobą dodanie kilku adnotacji JPA (bądź ustawieniem odpowiedniej konfiguracji w pliku orm.xml). Wybieram podejście pierwsze - dodanie adnotacji. Zmienił się również konstruktor, który niepotrzebuje już parametru wejściowego numer, który od tej pory jest generowany automatycznie przez JPA.

package pl.jaceklaskowski.konwerter;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
@NamedQuery(name = "Pracownik.wszyscyPracownicy", query = "SELECT p FROM Pracownik p")
public class Pracownik implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long numer;

private String imie;

@Temporal(TemporalType.DATE)
private Date zatrudnionyOd;

public Pracownik() {
}

public Pracownik(String imie, Date zatrudnionyOd) {
this.imie = imie;
this.zatrudnionyOd = zatrudnionyOd;
}

public long getNumer() {
return numer;
}

public void setNumer(long numer) {
this.numer = numer;
}

public String getImie() {
return imie;
}

public void setImie(String imie) {
System.out.println("setImie(" + imie + ")");
this.imie = imie;
}

public Date getZatrudnionyOd() {
return zatrudnionyOd;
}

public void setZatrudnionyOd(Date zatrudnionyOd) {
System.out.println("setZatrudnionyOd(" + zatrudnionyOd + ")");
this.zatrudnionyOd = zatrudnionyOd;
}

@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Pracownik other = (Pracownik) obj;
if (this.numer != other.numer) {
return false;
}
if (this.imie.equals(other.imie) && (this.imie == null || !this.imie.equals(other.imie))) {
return false;
}
if (this.zatrudnionyOd != other.zatrudnionyOd && (this.zatrudnionyOd == null || !this.zatrudnionyOd.equals(other.zatrudnionyOd))) {
return false;
}
return true;
}

@Override
public int hashCode() {
int hash = 5;
hash = 47 * hash + (int) (this.numer ^ (this.numer >>> 32));
hash = 47 * hash + (this.imie != null ? this.imie.hashCode() : 0);
hash = 47 * hash + (this.zatrudnionyOd != null ? this.zatrudnionyOd.hashCode() : 0);
return hash;
}
}
Metody equals() oraz hashCode(),które zazwyczaj są pomijane, w tym przypadku są kluczowe. Podczas kontroli poprawności wyboru (w etapie przetwarzania zlecenia JSF - uruchomienie kontrolerów - ang. process validations) tworzone są nowe egzemplarze Pracowników, które bez użycia wspomnianych metod, powodowałyby błąd kontroli. Gdyby, więc polegać na domyślnej implementacji metod, pojawiałby się błąd kontroli, gdyż sprawdzenie, czy wybrana opcja była na liście opcji odbywa się między innymi na podstawie porównania egzemplarzy przy pomocy metody equals(). Nieufnym polecam uruchomienie przykładu bez wspomnianych metod - equals() oraz hashCode(), a dla dociekliwych taką modyfikację aplikacji, aby equals() straciło na znaczeniu (podpowiedź: aktualna żywotność ziarna to pojedyńcze zlecenie - request).

Po zmianie konwertera modyfikuję ziarno zarządzane pracownikAgent, które jest reprezentowane przez klasę pl.jaceklaskowski.konwerter.PracownikAgent.

package pl.jaceklaskowski.konwerter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.faces.event.ActionEvent;
import javax.faces.model.SelectItem;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

public class PracownikAgent {

private Pracownik pracownik;

@PersistenceContext
private EntityManager em;

public PracownikConverter getKonwerterDlaPracownika() {
return new PracownikConverter(em);
}

public void wykonajAkcje(ActionEvent event) {
System.out.println("Wykonano akcję z pracownikiem: " + pracownik);
}

public Collection<SelectItem> getPracownicySelectItems() {
Collection<SelectItem> pracownicy = new ArrayList<SelectItem>();
for (Pracownik p : getPracownicy()) {
pracownicy.add(new SelectItem(p, p.getImie()));
}
return pracownicy;
}

public List<Pracownik> getPracownicy() {
return em.createNamedQuery("Pracownik.wszyscyPracownicy").getResultList();
}

public Pracownik getPracownik() {
return pracownik;
}

public void setPracownik(Pracownik pracownik) {
this.pracownik = pracownik;
}
}
Zmiana polega na dodaniu właściwości konwerterDlaPracownika (metoda odczytująca - getKonwerterDlaPracownika()) oraz pola egzemplarza em, w której serwer aplikacji umieści zarządcę encji korzystając z adnotacji @PersistenceContext. Zmodyfikowana została również metoda getPracownicy(), która opiera swoje działanie na JPA poprzez zapytanie nazwane (mianowane) - Pracownik.wszyscyPracownicy - do pobrania listy wszystkich pracowników.

Wykorzystanie zarządcy encji wymaga stworzenia pliku konfiguracji JPA - persistence.xml.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="Konwerter-czesc6PU" transaction-type="JTA">
<jta-data-source>jdbc/sample</jta-data-source>
<properties>
<property name="toplink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
Przechodzę do utworzenia strony strona6.jsp.

<%@page contentType="text/html" pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!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>Mini-aplikacja JSF - konwerter</title>
</head>
<body>
<f:view>
<h:form>
<h:selectOneMenu value="#{pracownikAgent.pracownik}"
converter="#{pracownikAgent.konwerterDlaPracownika}">
<f:selectItems value="#{pracownikAgent.pracownicySelectItems}" />
</h:selectOneMenu>
<br/>
<h:commandButton value="Zatwierdź" actionListener="#{pracownikAgent.wykonajAkcje}"/>
<br/>
<h:messages showSummary="true" errorStyle="color: red" />
</h:form>
</f:view>
</body>
</html>
Na stronie korzystam z przekazania konwertera do kontrolki h:selectOneMenu poprzez #{pracownikAgent.konwerterDlaPracownika}, czyli wywołanie metody PracownikAgent.getKonwerterDlaPracownika(), która z kolei utworzy nowy egzemplarz konwertera z przekazanym zarządcą encji.

W pliku faces-config.xml usuwamy sekcję dotyczącą związania konwertera PracownikConverter z klasą Pracownik.

<?xml version='1.0' encoding='UTF-8'?>

<faces-config version="1.2"
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-facesconfig_1_2.xsd">
<managed-bean>
<managed-bean-name>pracownik</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.konwerter.Pracownik</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>pracownikAgent</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.konwerter.PracownikAgent</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>
I to wszystkie zmiany, aby oprzeć działanie konwertera na JPA i wykorzystać bazę danych z prawdziwego zdarzenia. Pozostało jeszcze kilka ciekawych usprawnień, które mógłbym wprowadzić do aplikacji, a które wynikają z zastosowania JPA, jednakże miałoby to niewielki związek z tematem przewodnim, więc pozostawiam je na później.

Dla pełności obrazu wprowadzonych zmian należy jeszcze wspomnieć o pewnej funkcjonalności, która służy jedynie do wypełnienia bazy danych pracownikami, aby można było w pełni zaprezentować działanie aplikacji. W obecnej konfiguracji każdorazowe uruchomienie aplikacji kasuje bazę danych i tworzy schemat ponownie. Pusta baza danych pracowników nie pozwoliłaby na wybór pracownika w liście rozwijalnej. Dla obejścia tej niedogodności, podczas uruchomienia aplikacji internetowej, deklaruję wykonywanie słuchacza pl.jaceklaskowski.konwerter.DodajPracownikowListener.

package pl.jaceklaskowski.konwerter;

import java.util.Calendar;
import java.util.Date;
import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.transaction.UserTransaction;

public class DodajPracownikowListener implements ServletContextListener {

@PersistenceContext
EntityManager em;

@Resource
UserTransaction ut;

public void contextInitialized(ServletContextEvent event) {
System.out.println("Dodaję pracowników do bazy danych");
Date dzisiaj = Calendar.getInstance().getTime();
Pracownik agata = new Pracownik("Agatka", dzisiaj);
Pracownik jacek = new Pracownik("Jacek", dzisiaj);
try {
ut.begin();
em.persist(em.merge(agata));
em.persist(em.merge(jacek));
ut.commit();
} catch (Exception e) {
System.out.println("Ignorowany wyjątek: " + e.getLocalizedMessage());
}
}

public void contextDestroyed(ServletContextEvent event) {
}
}
, który zarejestrowany jest w głównym pliku konfiguracyjnym aplikacji internetowej - 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">
<context-param>
<param-name>com.sun.faces.verifyObjects</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.validateXml</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<listener>
<listener-class>pl.jaceklaskowski.konwerter.DodajPracownikowListener</listener-class>
</listener>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
</web-app>
Ciekawostką słuchacza DodajPracownikowListener jest skorzystanie z adnotacji @PersistenceContext oraz @Resource do wykorzystania z JPA.

Uruchomienie aplikacji nie zmienia się w stosunku do poprzednich części, więc pozostawiam to jako zadanie domowe. Przedstawię jedynie fragment dziennika zdarzeń Glassfish, który wskazuje na wykorzystywanego dostawcę JPA jakim jest TopLink Essentials 2.0.

Initializing Sun's JavaServer Faces implementation (1.2_04-b20-p03) for context '/konwerter'
Dodaję pracowników do bazy danych
TopLink, version: Oracle TopLink Essentials - 2.0 (Build b58g-fcs (09/07/2007))
Server: unknown
file:/Konwerter-czesc6/build/web/WEB-INF/classes/-Konwerter-czesc6PU login successful
Wykonano akcję z pracownikiem: pl.jaceklaskowski.konwerter.Pracownik@b73d3e3
Gotowy projekt aplikacji Konwerter dostępny jest jako jsf-konwerter-czesc6.zip.

Tym samym zakończyłem prezentację konwerterów JSF wieńcząc dzieło użyciem JPA. Dalsze udogodnienia mogłyby wykorzystać kolejne możliwości EJB3, m.in. ziarno bezstanowe do interakcji z JPA w celu usunięcia użycia transakcji po stronie aplikacji internetowej, itp. Wszelkie sugestie mile widziane.

Niepokoi mnie brak komentarzy do poszczególnych części serii o konwerterach JSF. Czyżby wszystko było jasne i działało od pierwszego uruchomienia?! Z ankiety Czy odpowiada Ci sposób prezentacji tematu w postaci krótkich części jak Konwertery JSF? wydaje się, że prezentacja tematu zdaje się być odpowiednia dla 14 osób, co cieszy, jednakże aż 3 osoby nie są w pełni zadowolone. Dlaczego? Dobrze byłoby podnieść swój warsztat techniczny uwzględniając ich uwagi (a może niekoniecznie techniczy, a literacki ;-)).

Przy okazji, dzisiejsze otwarcie NetBeans IDE 6.0 Nightly prezentuje Start Page z tłumaczeniem mojego artykułu Building an EJB 3.0 application using GlassFish v2, Apache Maven 2 and NetBeans IDE 6.0 (polska wersja to Tworzenie aplikacji EJB 3.0 z GlassFish v2, Apache Maven 2 i NetBeans IDE 6.0).

10 października 2007

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

0 komentarzy
Warszawska Grupa Użytkowników Technologii Java (Warszawa JUG) zaprasza na XVII spotkanie, które odbędzie się w nadchodzący wtorek 16.10.2007 o godzinie 18:00 w sali 4420 na Wydziale MIMUW przy ul. Banacha 2 w Warszawie.

Temat prezentacji: Jak to zrobić w Seamie?
Prowadzący: Paweł Wrzeszcz

O JBoss Seam mówi się, że jest "brakującym szkieletem" Javy EE 5.0, który w nowatorski sposób scala technologie EJB3, JPA, JSF 1.2 i in. w jedną całość ułatwiającą programiście szybkie wdrożenie się w tajniki i korzystanie z dobrodziejstw JEE.

W ramach wstępu dowiecie się jak za pomocą Seama stworzyć prostą aplikację webową Model-Widok-Kontroler. W części bardziej zaawansowanej zostaną pokazane dwa z wielu ciekawych udogodnień jakie oferuje nam Seam: wbudowana obsługa parametrów adresów URL oraz integracja z JavaScript (Seam Remoting Framework).

Prezentację poprowadzi Paweł Wrzeszcz, który kończy pisać pracę magisterską opartą o Seama na Wydziale MIMUW. Zawodowo Paweł jest członkiem zespołu JBoss Labs, pracuje nad infrastrukturą strony jboss.org.

Planowany czas prezentacji to 1,5 godziny z 15-minutową dyskusją. Na zakończenie (około 15 minut) planowana jest dyskusja dotycząca organizacji konferencji Warsjava organizowanej przez Warszawa JUG w dniu 17.11.2007.

Wstęp wolny

Zapraszam w imieniu Warszawa JUG!

Artykuł o EJB3, Glassfish, Maven2 i NetBeans 6 na netbeans.org

6 komentarzy
Nie mogłem wymarzyć sobie lepszej nagrody w zamian za napisanie artykułu Tworzenie aplikacji EJB 3.0 z GlassFish v2, Apache Maven 2 i NetBeans IDE 6.0 niż opublikowanie go na pierwszej stronie netbeans.org (!) Jakiś czas temu osoby z zespołu NetBeans zapytały mnie o zgodę na przetłumaczenie artykułu i jego publikację. Nie zastanawiałem się długo, bo i dlaczego miałbym?! Po kilku dniach i drobnych problemach w tłumaczeniu mojego pokrętnego toku myślenia udało się i artykuł znalazł się na netbeans.org. Zainteresowanych wersją angielską zapraszam na Building an EJB 3.0 application using GlassFish v2, Apache Maven 2 and NetBeans IDE 6.0. Pozdrowienia dla załogi NetBeans.

09 października 2007

Konwertery w JavaServer Faces część 5

1 komentarzy
Przez ostatnie 4 odcinki w serii Konwertery w JavaServer Faces, stworzyłem aplikację, która korzysta z konwersji danej typu znakowego reprezentującego pracownika do typu Pracownik wymaganego w ziarnie zarządzanym JSF. Za pomocą atrybutu converter kontrolki h:selectOneMenu związałem klasę konwertera z konkretną kontrolką i podczas przetwarzania zlecenia JSF, wykonanie go gwarantowało odpowiedni typ w atrybucie ziarna pracownikAgent.

Poza użyciem klasy pl.jaceklaskowski.konwerter.TymczasowaBazaDanych, którą zajmę się w kolejnej części, jeszcze jedno miejsce mogło niepokoić - właśnie użycie atrybutu converter, który związywał widok z klasą konwertera. Jeśliby się zastanowić nad tym bardziej, to użycie atrybutu jest istotne dla osoby dostarczającej metody biznesowe w ziarnie zarządzanym JSF - pracownikAgent, podczas gdy dla twórcy strony strona4.jsp jest to kompletnie obojętne jaki i czy w ogóle konwerter będzie wykorzystywany. Najlepiej, gdyby konwersja odbywała się niezauważalnie dla obu stron - dostawcy metody biznesowej i autora strony (potencjalne miejsce na zrównoleglenie prac projektowych, gdzie kolejna osoba w zespole mogłaby pracować nad konwerterem bez wpływu na pozostałych członków). W/g mojego rozumienia wzorca Model-View-Controller (MVC), który służy do oddzielenia warstw w aplikacji, rolę konwersji bierze na siebie część kontrolera, który zarządza modelem i koordynuje widokami. W JSF rolę kontrolera przejmuje plik faces-config.xml (dokładniej jest to javax.faces.application.NavigationHandler i jego świta, którzy powodują, że JSF może przyjąć różne postacie, np. JBoss Seam, czy facelets, ale to jest temat na inny serial). Nie wchodząc w szczegóły, jeśli poszukujemy miejsca konfiguracji kontrolera to właśnie faces-config.xml jest tym miejscem.

<?xml version='1.0' encoding='UTF-8'?>

<faces-config version="1.2"
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-facesconfig_1_2.xsd">
<converter>
<converter-for-class>pl.jaceklaskowski.konwerter.Pracownik</converter-for-class>
<converter-class>pl.jaceklaskowski.konwerter.PracownikConverter</converter-class>
</converter>
<managed-bean>
<managed-bean-name>pracownik</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.konwerter.Pracownik</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>pracownikAgent</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.konwerter.PracownikAgent</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>
Na pierwszy rzut oka różnica między obecną postacią faces-config.xml, a poprzednią może nie być widoczna. Jest to użycie elementu converter-for-class zamiast poprzedniego converter-id. Element converter-for-class związuje klasę konwertera z danym typem dla każdego jego wystąpienia w aplikacji, co niweluje potrzebę przypisywania go na stronie JSF.

<%@page contentType="text/html" pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!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>Mini-aplikacja JSF - konwerter</title>
</head>
<body>
<f:view>
<h:form>
<h:selectOneMenu value="#{pracownikAgent.pracownik}">
<f:selectItems value="#{pracownikAgent.pracownicySelectItems}" />
</h:selectOneMenu>
<br/>
<h:commandButton value="Zatwierdź" actionListener="#{pracownikAgent.wykonajAkcje}"/>
<br/>
<h:messages showSummary="true" errorStyle="color: red" />
</h:form>
</f:view>
</body>
</html>
Różnicą w powyższej stronie strona5.jsp względem poprzedniej strona4.jsp jest usunięcie atrybutu converter. W ten sposób nasza strona nie wie, czy i jaki konwerter zostanie uruchomiony podczas przypisywania danych ze strony do ziarna zarządzanego JSF pracownikAgent. Zmiana niewielka, a zmniejsza powiązania między poszczególnymi elementami aplikacji opartej o JSF. Pozostałe elementy aplikacji pozostają niezmienione, co czyni tę zmianę nieinwazyjną acz bardzo potężną w możliwościach i skutkach, gdyż przypisanie konwertera do wszystkich miejsc użycia danego typu zawsze go aktywuje, podczas gdy poprzednie użycie konwertera było miejscowe, wybiórcze.

Uruchomienie aplikacji nie zmienia się w stosunku do poprzedniej części. Nieprzekonanych namawiam do uruchomienia samodzielnego w ramach zadania domowego i podzielenia się wnioskami.

Gotowy projekt aplikacji Konwerter dostępny jest jako jsf-konwerter-czesc5.zip.

08 października 2007

Konwertery w JavaServer Faces część 4

5 komentarzy
Poprzednio skończyłem na aplikacji, która korzysta z domyślnej konwersji pozycji w menu rozwijalnym h:selectOneMenu do typu atrybutu ziarna zarządzanego #{pracownikAgent.wybranyPracownik}. Atrybut wybranyPracownik jest typu java.lang.String, a pozycje na liście reprezentowne są przez typ long, które odpowiadają numerom pracowników. Wybór typu identyfikatora pozycji w menu rozwijalnym jest decyzją programisty aplikacji JSF i jest wyznaczony przez pierwszy argument wejściowy konstruktora javax.faces.model.SelectItem (w tym przypadku był to long).

Ktoś mógłby zapytać, nad czym się tutaj zastanawiać? Możnaby przecież w miejscu wykorzystania wybranej pozycji menu przez użytkownika, tj. w metodzie obsługującej akcję, po prostu zamienić typ wejściowy na wymagany (w naszym przypadku String na long) i wykonać operację wyszukania pracownika po jego numerze. Właśnie! Wszystko można wykonać samodzielnie, ale czyż nie byłoby przyjemniej, jeśli część pracy możnaby wydelegować - złożyć na barki szkieletu programistycznego, np. JSF? Właśnie w tym tkwi sedno sprawy. Czym więcej wiemy o wykorzystywanym rozwiązaniu/szkielecie, tym więcej możemy z niego wykorzystać, zmniejszając ilość pracy włożonej w stworzenie aplikacji (w końcu to był powód zastosowania tego rozwiązania względem innego, odrzuconego, nieprawdaż?).

Moim zamierzeniem jest stworzenie aplikacji w taki sposób, aby numer pracownika był identyfikatorem pozycji w menu rozwijalnym i był wykorzystywany do tworzenia widoku aplikacji, a w wywołanej akcji (np. po wciśnięciu przycisku Zatwierdź) oczekiwałbym egzemplarza typu Pracownik odpowiadającego wybranej pozycji w menu.

Pierwszą z wprowadzonych zmian w mojej dotychczasowej aplikacji JSF będzie wprowadzenie nowej strony strona4.jsp.

<%@page contentType="text/html" pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!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>Mini-aplikacja JSF - konwerter</title>
</head>
<body>
<f:view>
<h:form>
<h:selectOneMenu value="#{pracownikAgent.pracownik}"
converter="konwerter.pracownik">
<f:selectItems value="#{pracownikAgent.pracownicySelectItems}" />
</h:selectOneMenu>
<br/>
<h:commandButton value="Zatwierdź" actionListener="#{pracownikAgent.wykonajAkcje}"/>
<br/>
<h:messages showSummary="true" errorStyle="color: red" />
</h:form>
</f:view>
</body>
</html>
Główną różnicą między stroną strona3.jsp z poprzedniej części, a powyższą, jest skorzystanie z atrybutu converter kontrolki h:selectOneMenu. Atrybut converter wskazuje na konwerter zarejestrowany w konfiguracji aplikacji JSF - faces-config.xml.

<?xml version='1.0' encoding='UTF-8'?>

<faces-config version="1.2"
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-facesconfig_1_2.xsd">
<converter>
<converter-id>konwerter.pracownik</converter-id>
<converter-class>pl.jaceklaskowski.konwerter.PracownikConverter</converter-class>
</converter>
<managed-bean>
<managed-bean-name>pracownik</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.konwerter.Pracownik</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>pracownikAgent</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.konwerter.PracownikAgent</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>
W Konwertery w JavaServer Faces część 1 przedstawiłem wymagania stawiane konwerterowi - jest to po prostu obiekt realizujący interfejs javax.faces.convert.Converter.

package pl.jaceklaskowski.konwerter;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;

public class PracownikConverter implements Converter {

public Object getAsObject(FacesContext context, UIComponent component, String value) {
long numerPracownika = Long.valueOf(value);
return TymczasowaBazaDanych.odszukaj(numerPracownika);
}

public String getAsString(FacesContext context, UIComponent component, Object value) {
return Long.toString(((Pracownik) value).getNumer());
}
}
Podczas zatwierdzenia formularza przez użytkownika (wciśnięcie przycisku Zatwierdź) JSF wykona pierwsze stadium przetwarzania zlecenia, w którym wykonane zostaną konwersje danych ze strony do odpowiadającym ich typom atrybutów, tj. identyfikator pozycji w menu (typu String) zostanie zamieniony przez konwerter konwerter.pracownik na typ Pracownik i wyłącznie poprawna konwersja spowoduje przypisanie wyboru atrybutowi wskazanemu przez wyrażenie #{pracownikAgent.pracownik}. Na zakończenie przetwarzania zlecenia JSF wykona związaną z przyciskiem akcję wykonajAkcje, a po pomyślnym jej wykonaniu ponownie wyświetli stronę (w przypadku zastosowania wyłącznie akcji-słuchacza wskazanej przez atrybut actionListener kontrolki h:commandButton zostanie wyświetlona strona inicjująca zlecenie).

Dla pełnego obrazu zmian należy wspomnieć o tych, w ziarnie pracownikAgent reprezentowanym przez klasę pl.jaceklaskowski.konwerter.PracownikAgent.

package pl.jaceklaskowski.konwerter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.faces.event.ActionEvent;
import javax.faces.model.SelectItem;

public class PracownikAgent {

private Pracownik pracownik;

public void wykonajAkcje(ActionEvent event) {
System.out.println("Wykonano akcję z pracownikiem: " + pracownik);
}

public Collection<SelectItem> getPracownicySelectItems() {
Collection<SelectItem> pracownicy = new ArrayList<SelectItem>();
for (Pracownik p : getPracownicy()) {
pracownicy.add(new SelectItem(p, p.getImie()));
}
return pracownicy;
}

public List<Pracownik> getPracownicy() {
return TymczasowaBazaDanych.zwrocWszystkichPracownikow();
}

public Pracownik getPracownik() {
return pracownik;
}

public void setPracownik(Pracownik pracownik) {
this.pracownik = pracownik;
}
}
Istotna zmiana w ziarnie pracownikAgent polega na przekazaniu pełnego egzemplarza Pracownik p do konstruktora javax.faces.model.SelectItem podczas tworzenia listy pozycji w menu (dla przypomnienia poprzednio był to wyłącznie numer typu long).

Uruchomienie aplikacji rozpoczyna się od wyświetlenia strony strona4.jsp.

Wybranie pozycji w menu oraz wciśnięcie przycisku Zatwierdź spowoduje wyświetlenie wybranej pozycji typu Pracownik na konsolę serwera aplikacji.

deployed with moduleid = konwerter
Initializing Sun's JavaServer Faces implementation (1.2_04-b20-p03) for context '/konwerter'
Wykonano akcję z pracownikiem: pl.jaceklaskowski.konwerter.Pracownik@56c88c
Jak można zauważyć akcja pracuje już z właściwym egzemplarzem typu Pracownik, więc poprzez zastosowanie konwerterów akcja, a w zasadzie jej autor, nie musi troszczyć się o odszukiwanie potrzebnych do jej działania informacji, co znacząco uprości jego pracę podczas realizacji działania metody biznesowej wykonajAkcje.

Warto oczywiście rozważyć przesłonięcie metody toString(), aby wyświetlić coś sensowniejszego niż pl.jaceklaskowski.konwerter.Pracownik@56c88c.

Po tych zmianach pozostaje usunąć tymczasowe wprowadzenie typu pl.jaceklaskowski.konwerter.TymczasowaBazaDanych i zastąpienie go...właśnie, czy są już przeczucia odnośnie tego, co będzie w kolejnej odsłonie? Dla podpowiedzi napiszę, że będzie to blisko związane z rzeczami, które mnie ostatnimi czasy bardzo absorbują, a które są w ofercie Korporacyjnej Javy 5. Niebawem rozwiązanie zagadki.

Gotowy projekt aplikacji Konwerter dostępny jest jako jsf-konwerter-czesc4.zip.

07 października 2007

Konwertery w JavaServer Faces część 3

1 komentarzy
Ostatni odcinek z serii Konwertery w JavaServer Faces - Konwertery w JavaServer Faces część 2 - zakończyłem na przedstawieniu konwertera dostarczanego przez JSF - f:convertDateTime. Dzięki f:convertDateTime mogłem skoncentrować się na tworzeniu aplikacji, nie przywiązując dużej uwagi do konwersji danych między użytkownikiem a aplikacją, tj. na stronie JSF wyświetlana była data w postaci ciągu znaków, podczas gdy w aplikacji oczekiwałem typu java.util.Date. Zamiast trudzić się konwersją samodzielnie, zleciłem JSF wykonanie jej za mnie. Dzięki f:convertDateTime mogłem zadeklarować czy interesuje mnie pełna data z godziną, czy wyłącznie dzień oraz jej format. Podkreślam słowo deklaracja jako nakreślenie moich wymagań w aplikacji JSF (cecha deklaratywności wymagań jest często zapominana w przedstawianiu technologii Korporacyjnej Javy, a przecież jest jej jednym z główych atutów). Poprzez deklaratywne określenie wymagań, zlecam ich realizację JSF, a nie samemu się tym param. Zaleta jest oczywista: mniej czasu muszę spędzić na budowaniu aplikacji.

W tym odcinku przedstawię kolejną funkcjonalność JSF dotyczącą konwersji danych, chociaż tak na prawdę to przygotuję podłoże dla jej wprowadzenia w jeszcze kolejnym odcinku (niejeden z zaawansowanych użytkowników JSF zapewne zastanawia się teraz, ile jeszcze można opisywać konwertery - jedynie niewielki wycinek funkcjonalności JSF. Z radością oznajmiam, że długo, bardzo długo ;-)).

Rozpocznę od modyfikacji aplikacji i wprowadzeniu nowej strony strona3.jsp, która od tej pory stanie się stroną główną aplikacji.

<%@page contentType="text/html" pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!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>Mini-aplikacja JSF - konwerter</title>
</head>
<body>
<f:view>
<h:form>
<h:selectOneMenu id="pracownik" value="#{pracownikAgent.wybranyPracownik}">
<f:selectItems value="#{pracownikAgent.pracownicySelectItems}" />
</h:selectOneMenu>
<h:message for="pracownik" errorStyle="color: red"/>
<br/>
<h:commandButton value="Zatwierdź" actionListener="#{pracownikAgent.wykonajAkcje}"/>
<br/>
<h:messages showSummary="true" errorStyle="color: red" />
</h:form>
</f:view>
</body>
</html>
Strona zawiera kilka nowych kontrolek, których przedstawienie pomoże w zrozumieniu tematu głównego - konwerterów JSF. Zacznę od h:selectOneMenu. Jej zadaniem jest wyświetlenie listy rozwijalnej zbudowanej na bazie listy dostarczanej przez kolejną kontrolkę f:selectItems i zapisanie wyboru w obiekcie wskazanym przez value (w tym przypadku będzie to atrybut wybranyPracownik ziarna zarządzanego pracownikAgent). Lista pozycji dostarczana jest przez kontrolkę f:selectItems z listy dostarczanej przez obiekt wskazany przez atrybut value. Elementy listy muszą być typu javax.faces.model.SelectItem, z których pobiera się dane potrzebne do stworzenia listy, np. identyfikator, wyświetlany tekst, itp.

Lista pozycji w menu oraz wybór użytkownika kontrolowane są przez ziarno zarządzane pracownikAgent, który reprezentowany jest przez klasę pl.jaceklaskowski.konwerter.PracownikAgent.

package pl.jaceklaskowski.konwerter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.faces.event.ActionEvent;
import javax.faces.model.SelectItem;

public class PracownikAgent {

private String wybranyPracownik;
private Pracownik pracownik;

public void wykonajAkcje(ActionEvent event) {
System.out.println("Wykonano akcję z pracownikiem: " + wybranyPracownik);
}

public Collection<SelectItem> getPracownicySelectItems() {
Collection<SelectItem> pracownicy = new ArrayList<SelectItem>();
for (Pracownik p : getPracownicy()) {
pracownicy.add(new SelectItem(p.getNumer(), p.getImie()));
}
return pracownicy;
}

public List<Pracownik> getPracownicy() {
return TymczasowaBazaDanych.zwrocWszystkichPracownikow();
}

public String getWybranyPracownik() {
return wybranyPracownik;
}

public void setWybranyPracownik(String wybranyPracownik) {
this.wybranyPracownik = wybranyPracownik;
}

public Pracownik getPracownik() {
return pracownik;
}

public void setPracownik(Pracownik pracownik) {
this.pracownik = pracownik;
}
}
Każdy z atrybutów value kontrolek h:selectOneMenu oraz f:selectItems związany jest z metodą odczytująca (ang. getter) oraz, w przypadku h:selectOneMenu, modyfikująca (ang. setter).

Wciśnięcie przycisku Zatwierdź spowoduje wykonywanie akcji-słuchacza wykonajAkcje(). Akcja operuje na wybranym użytkowniku z listy rozwijalnej. I właśnie to będzie przedmiotem dalszej prezentacji konwerterów JSF. Wybór użytkowika to wybór identyfikatora pozycji na liście. Użytkownik wybiera pozycję Jacek, jednakże dla programisty aplikacji dostępny jest jako "0" (typu znakowego nie numerycznego!). Sposób reprezentacji pozycji w liście zależy od wyboru konstrukcji listy z SelectItem, która jest zwracana przez metodę PracownikAgent.getPracownicySelectItems() (odpowiada temu wyrażenie pracownikAgent.pracownicySelectItems na stronie JSF).

Warto wspomnieć o wykorzystaniu atrybutu actionListener kontrolki h:commandButton. Atrybut actionListener wymaga wskazania akcji-słuchacza, która będzie wywołana podczas aktywacji kontrolki (w tym przypadku wciśnięcia przycisku), tj. metody o sygnaturze public void actionListener(javax.faces.event.ActionEvent). Jest istotna różnica w jej korzystaniu versus metody wskazanej przez atrybut action, jednakże jest to temat sam w sobie i pozostawiam jego zgłębienie zainteresowanym (zachęcam do dzielenia się odpowiedziami jako komentarze do notatki).

I na zakończenie prezentacji zmian, wspomnę o klasie pl.jaceklaskowski.konwerter.TymczasowaBazaDanych, która jak sama nazwa wskazuje jest tymczasowa, aż na scenę wkroczy JPA (a już nie mogę doczekać się tego momentu). Jej celem jest dostarczanie danych dla aplikacji - taka mini baza danych. Nie ma się czym zajmować, bo pójdzie w odstawkę niebawem.

package pl.jaceklaskowski.konwerter;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TymczasowaBazaDanych {

private static Map<Long, Pracownik> bazaDanychPracownikow = new HashMap<Long, Pracownik>();
static {
Date dzisiaj = Calendar.getInstance().getTime();
Pracownik jacek = new Pracownik(0, "Jacek", dzisiaj);
Pracownik agata = new Pracownik(1, "Agatka", dzisiaj);
bazaDanychPracownikow.put(jacek.getNumer(), jacek);
bazaDanychPracownikow.put(agata.getNumer(), agata);
}

public static List<Pracownik> zwrocWszystkichPracownikow() {
return new ArrayList<Pracownik>(bazaDanychPracownikow.values());
}

public static void wstaw(Pracownik pracownik) {
bazaDanychPracownikow.put(pracownik.getNumer(), pracownik);
}

public static Pracownik odszukaj(long numer) {
return bazaDanychPracownikow.get(numer);
}
}
Pozostaje zaprezentować konfigurację aplikacji JSF - faces-config.xml.

<?xml version='1.0' encoding='UTF-8'?>

<faces-config version="1.2"
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-facesconfig_1_2.xsd">
<managed-bean>
<managed-bean-name>pracownik</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.konwerter.Pracownik</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>pracownikAgent</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.konwerter.PracownikAgent</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>
Uruchomienie aplikacji to wyświetlenie strony strona3.jsp.

, gdzie zatwierdzenie wyboru przez wciśnięcie przycisku Zatwierdź będzie wiązało się z wyświetleniem wybranej pozycji jako...wartość znakowa (de facto jest to liczba opakowana jako ciąg znaków).

deployed with moduleid = konwerter
Initializing Sun's JavaServer Faces implementation (1.2_04-b20-p03) for context '/konwerter'
Wykonano akcję z pracownikiem: 1
1 zamiast Agata, czy pełnego egzemplarza typu Pracownik?! Czy tego potrzebuje programista JSF? Niekoniecznie. Rozwiązanie tego problemu w kolejnej odsłonie artykułu. Na zakończenie dodam, że wciąż niewykorzystałem pełni możliwości drzemiących w JSF i rozwiązanie będzie oparte o jedną z nich. JSF ma jeszcze wiele do zaoferowania zdejmując trud konwersji z barków twórcy aplikacji.

Gotowy projekt aplikacji Konwerter dostępny jest jako jsf-konwerter-czesc3.zip.

06 października 2007

Konwertery w JavaServer Faces część 2

2 komentarzy
Aplikacja z pierwszej części artykułu o konwerterach w JavaServer Faces (JSF) - Konwertery w JavaServer Faces część 1 - korzystała z domyślnej konwersji dostarczanej przez implementację JSF. Wciskając przycisk Zatwierdź implementacja JSF wykonała konwersję wprowadzonego imienia pracownika do typu odpowiadającego typowi właściwości imie ziarna zarządzanego pracownik. Dla wielu typów konwersja wykonywana jest automatycznie, niezauważalnie dla programisty JSF.

W kolejnej części przedstawię wykorzystanie dostępnego w JSF konwertera między typem znakowym a typem kalendarzowym (datą).

Rozpocznę od dodania nowej właściwości zatrudnionyOd w ziarnie pracownik typu java.util.Date.

package pl.jaceklaskowski.konwerter;

import java.util.Date;

public class Pracownik {

private long numer;
private String imie;
private Date zatrudnionyOd;

public Pracownik() {
}

public Pracownik(long numer, String imie, Date zatrudnionyOd) {
this.numer = numer;
this.imie = imie;
this.zatrudnionyOd = zatrudnionyOd;
}

public long getNumer() {
return numer;
}

public void setNumer(long numer) {
this.numer = numer;
}

public String getImie() {
return imie;
}

public void setImie(String imie) {
System.out.println("setImie(" + imie + ")");
this.imie = imie;
}

public Date getZatrudnionyOd() {
return zatrudnionyOd;
}

public void setZatrudnionyOd(Date zatrudnionyOd) {
System.out.println("setZatrudnionyOd(" + zatrudnionyOd + ")");
this.zatrudnionyOd = zatrudnionyOd;
}
}

Kolejna zmiana polegała będzie na dodaniu pola, w którym użytkownik będzie wprowadzał datę zatrudnienia pracownika. Strona o nazwie strona2.jsp będzie od tej pory stroną startową aplikacji. Dodałem również kontrolkę h:messages, aby wyświetlane były komunikaty błędów, jeśli takie wystąpią.

<%@page contentType="text/html" pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!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>Mini-aplikacja JSF - konwerter</title>
</head>
<body>
<f:view>
<h:form>
Podaj imię pracownika <h:inputText value="#{pracownik.imie}" />
<br>
Zatrudniony od
<h:inputText value="#{pracownik.zatrudnionyOd}" />
<br/>
<h:commandButton value="Zatwierdź" />
<br/>
<h:messages showSummary="true" />
</h:form>
</f:view>
</body>
</html>

Uruchomienie aplikacji i zatwierdzenie wprowadzonej daty przez wciśnięcie przycisku Zatwierdź spowoduje wyświetlenie strony z błędem Conversion Error setting value '06.10.2007' for 'null Converter'.

Ten niezrozumiały komunikat tłumaczy się jako brak konwertera między typami znakowym a kalendarzowym. Rozwiązaniem polega na dodaniu konwertera, który jest dostarczany przez JSF - f:convertDateTime. Konwertery działają w ramach kontrolki, w której zostały zdefiniowane, więc związanie konwertera f:convertDateTime wykonane jest przez umieszczenie go w ramach kontrolki h:inputText, dla której będzie wywołany podczas stadiów rozwojowych - ustawienie wartości zlecenia oraz wyświetlenie odpowiedzi.

Konwerter f:convertDateTime udostępnia atrybuty, dzięki którym można kontrolować konwersję, m.in. style daty dateStyle i czasu timeStyle, ustawień regionalnych (ang. locale) - locale oraz wzorzec pattern.

<%@page contentType="text/html" pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!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>Mini-aplikacja JSF - konwerter</title>
</head>
<body>
<f:view>
<h:form>
Podaj imię pracownika <h:inputText id="imie" value="#{pracownik.imie}" />
<h:message for="imie" errorStyle="color: red"/>
<br>
Zatrudniony od
<h:inputText id="zatrudnionyOd" value="#{pracownik.zatrudnionyOd}">
<f:convertDateTime dateStyle="short" pattern="dd.MM.yyyy"/>
</h:inputText>
(format: dd.MM.yyyy)
<h:message for="zatrudnionyOd" errorStyle="color: red"/>
<br/>
<h:commandButton value="Zatwierdź" />
<br/>
<h:messages showSummary="true" errorStyle="color: red" />
</h:form>
</f:view>
</body>
</html>

Zatwierdzenie poprawnej daty, np. 06.10.2007 nie spowoduje już wyświetlenia błędu

Na konsoli serwera aplikacji można zauważyć następujące komunikaty:

deployed with moduleid = konwerter
Initializing Sun's JavaServer Faces implementation (1.2_04-b20-p03) for context '/konwerter'
setImie(Jacek)
setZatrudnionyOd(Sat Oct 06 00:00:00 CEST 2007)

Na stronie strona2.jsp użyłem kilka ciekawostek JSF - atrybut id w kontrolkach h:inputText oraz związałem z nimi dedykowane kontrolki h:message, które wyświetlają komunikaty błędu dla pojedyńczej kontrolki JSF wskazanej przez atrybut for. Wpisanie niepoprawnej daty lub użycie niepoprawnego formatu daty spowoduje wyświetlenie komunikatu błędu w kolorze czerwonym obok kontrolki h:inputText, którego komunikat błędu dotyczy.

Warto również zauważyć, że ziarno pracownik nie zostanie w ogóle wywołane, właśnie ze względu na błędy danych. Jest to kolejne ułatwienie przy tworzeniu aplikacji, które dostarcza JSF.

Pozostaje uzupełnić przykład o zapewnienie, że imię i data zatrudnienia zostaną faktycznie wprowadzone przez użytkownika oraz zmodyfikowanie komunikatu błędu, który nie zawierałby identyfikatorów pól, np. j_id_id15:zatrudnionyOd, a ich przyjemniejsze dla użytkownika odpowiedniki jak i sam komunikat błędu zależny od języka użytkownika. Odpowiedzi w postaci komentarzy do notatki mile widziane.

Gotowy projekt aplikacji Konwerter dostępny jest jako jsf-konwerter-czesc2.zip.