23 marca 2008

Nowości NetBeans IDE 6.1 - JSF CRUD Generator

6 marca 2008 wyszła wersja NetBeans IDE 6.1 BETA - NetBeans IDE 6.1 Beta Now Available!. Jedyną z nowości, która zwróciła moją uwagę było JSF CRUD Generator (back by popular demand). Moją ciekawość dodatkowo spotęgował konkurs NetBeans IDE 6.1 Beta Blogging Contest, gdzie jak zrozumiałem wystarczy jedynie przedstawić NetBeans IDE 6.1 i tyle. Na uwagę zasługuje fakt, że Blogs will be accepted in the following languages a tam Polish! A skoro even if you don’t win, you might get a bunch of traffic! to nawet bez nagrody będę miał ten zysk, że potencjalnie mogę przyciągnąć uwagę kolejnych krytyków mojego spojrzenia na sprawy związane z Javą, jej korporacyjną wersją i projektami otwartymi. Sami się prosili ;-)

Rozpocząłem od lektury NetBeans IDE 6.1 Beta Information. Niestety niewiele można było tam znaleźć o JSF CRUD Generator. Szczęśliwie był tam zrzut ekranu z funkcjonalnością, która mnie zainteresowała, więc wiedziałem, że powinienem zacząć od asystenta JSF Pages From Entity Class. Zajrzałem również na stronę JsfCrudGenerator i znowu niewiele. Trochę informacji znalazłem również na stronie Milestones New and Noteworthy. W sumie głucho jak na funkcjonalność, którą wprowadzono do NetBeans back by popular demand. Chyba wszyscy wszystko wiedzą i stwierdzono, że popularność tej funkcjonalności nie zasługuje na szersze przedstawienie, bo co tutaj opisywać skoro wiadomo w co się gra. I tutaj wchodzę ja ;-)

Rozpoczynam od przygotowania bazy danych, którą w NetBeans (NB) jest Java DB. W zakładce Services w Databases znajduje się pozycja Java DB.


Za pomocą menu kontekstowego Start Server uruchamiam bazę Java DB.


Po chwili, po uruchomieniu bazy danych, uaktywni się menu Create Database....


Wypełniam danymi:
  • Database Name: piodb
  • User Name: jacek
  • Password: jacek


i zatwierdzam OK. Tym samym baza jest gotowa. Pojawi się kolejna pozycja w Databases reprezentująca połączenie z nowoutworzoną bazą piodb.


Pora trochę poprogramować. Pora na utworzenie dwóch encji Pytanie i Odpowiedz w relacji jeden-do-wielu. Zanim jednak do tego przejdziemy najpierw należy utworzyć ich projekt.

Wybieram zakładkę Projects, gdzie za pomocą kombinacji klawiszy Ctrl+Shift+N uruchamiam asystenta Java Class Library (kategoria Java - powinna być domyślnie wybrana). Jakkolwiek encje mogłyby być częścią aplikacji webowej, postanowiłem jednak wydzielić je jako oddzielny projekt do ponownego wykorzystania w innych projektach, niekoniecznie webowych (jeszcze nieokreślonych i trochę na wyrost, ale mam wrażenie, że ostatecznie takie podejście się opłaci).

W kolejnym kroku - Name and Location - wpisuję dane biblioteki.
  • Project Name: PioJPA


Zatwierdzam przyciskiem Finish.

Ctrl+N i wybieram kategorię Persistence, a w niej Entity Class.


Mógłbym wybrać asystenta Entity Classes from Database, ale skoro tworzę aplikację w podejściu top-down, gdzie najpierw aplikacja a później baza danych, gdzie baza danych jest "czysta", nie mam innego wyboru.

W kolejnym kroku podaję dane klasy encji Pytanie.
  • Class Name: Pytanie
  • Package: pl.jaceklaskowski.pio.encja
Ignoruję podpowiedź asystenta "The project does not have a persistence unit. You need a persistence unit to persist entity classes", gdyż "dostawcą" konfiguracji będzie aplikacja webowa, którą dopiero stworzymy, a projekt encji PioJPA poza użyciem adnotacji JPA nie będzie zależny od JPA. Nigdy nie wiadomo, gdzie ostatecznie zostanie użyty nasz projekt, a jeśli można go uniezależnić od użytego obecnie szkieletu programistycznego czy technologii tym lepiej dla późniejszego ponownego użycia.

Zatwierdzamy konfigurację przyciskiem Finish.

I tu pierwszy błąd NB 6.1 BETA - bez utworzenia PU zgodnie z podpowiedzią asystenta powoduje brak dodania biblioteki JPA i powoduje błąd składni klas (niedostępność użytych interfejsów JPA). Zgłoszone jako 130858: Using Entity Class wizard without creating PU leads to missing JPA interface errors.

Mamy kila możliwości obejścia tego błędu - skasować projekt i utworzyć go od nowa z jednoczesnym utworzeniem PU, ręcznie dodać bibliotekę JPA bądź ostatecznie stworzyć PU podczas tworzenia kolejnej encji, którą i tak mieliśmy utworzyć. Wybieramy jednak podejście numer 2, gdzie dodamy bibliotekę JPA i nie będziemy infekować projektu plikami jak persistence.xml, których i tak nie mieliśmy zamiaru używać. Czym mniej bałaganu tym lepiej.

Wybieramy Properties z menu kontekstowego projektu PioJPA.


Przechodzimy do Libraries, wciskamy przycisk Add Library... i po wybraniu biblioteki TopLink Essentials wciskamy przycisk Add Library.


Wciskamy przycisk OK i problem "odpływa".

Tworzę nową encję Odpowiedz. Ctrl+N, Persistence > Entity Class, gdzie podaję
  • Class Name: Odpowiedz
  • Package: pl.jaceklaskowski.pio.encja
Wracam do klasy encji Pytanie, gdzie umieszczam kolekcję Odpowiedzi (typ java.util.List). Uzupełniam import za pomocą Ctrl+SPACE bądź alternatywnie Ctrl+Shift+I do uzupełnienia importu java.util.List.


Stawiam kursor w ciele klasy Pytanie, Alt+Insert i wybieram menu Getter and Setter... do utworzenia metod get (odczytującej) i set (modyfikującej) dla atrybutu odpowiedzi (można dodatkowo skorzystać ze spacji do oznaczenia atrybutu, dla którego będą tworzone metody, co zniesie konieczność korzystania z pomocy myszki).

Wybieram atrybut odpowiedzi

i wciskam przycisk Generate.

Zgodnie z komunikatem NB przy metodzie Pytanie.getOdpowiedzi() (The multi-valued entity relation is not defined) dodaję adnotację @OneToMany do metody get.

Dodaję kolejny atrybut tresc (typu String) do klasy encji Pytanie i Odpowiedz. Tworzę odpowiadające mu metody get i set w obu klasach za pomocą Alt+Insert.

Praca nad projektem PioJPA zakończona (mimo komunikatu ostrzegawczego, że mamy encje, ale nie mamy zdefiniowanej jednostki trwałej - The project does not contain a persistence unit). Żegnam projekt PioJPA i przechodzę do utworzenia kolejnego - PioWeb.

Ctrl+Shirt+N i wybieram asystenta Web > Web Application.


W kolejnym kroku podaję:
  • Project Name: PioWeb
i rejestruję serwer aplikacyjny za pomocą przycisku Add... przy polu Server. Pozostawiam szczegóły tego kroku jako zadanie domowe.

Ustawiam Context Path na /pio.


Zatwierdzam przyciskiem Next >. I jeszcze raz Next >, aż pojawi się etap Frameworks, w którym wybieram Visual Web JavaServer Faces. Nigdy nie korzystałem z tej opcji, a naczytałem się, że jest to jedna z tych integracji, którą zespół NetBeans szczyci się szczególnie, więc pora jej skosztować.


Zatwierdzam Finish.

Po dłużej chwili pojawia się nowy projekt PioWeb w widoku Projects. Zauważalnie dłużej trwa inicjowanie biblioteki Woodstock, która jak rozumiem jest fundamentem dla Visual Web JavaServer Faces w NetBeans.

Związuję projekt PioWeb z PioJPA. PioJPA staje się biblioteką encji w projekcie PioWeb - menu kontekstowe Properties projektu PioWeb, a później wybieram Libraries i Add Project..., gdzie wybieram projekt PioJPA.


Teraz w końcu nadeszła pora na skorzystanie z dobrodziejstw funkcji JSF Pages from Entity Class. Ctrl+N, a następnie Persistence i JSF Pages from Entity Class.


Dodaję wszystkie dostępne encje - Odpowiedz i Pytanie za pomocą przycisku Add All >>.


Dopiero teraz utworzę jednostkę trwałą (PU - persistence unit) dla mojej aplikacji za pomocą przycisku Create Persistence Unit... Tworzę nowe źródło danych na serwerze aplikacyjnym (w tym przypadku jest to GlassFish).


Podaję JNDI Name: jdbc/piodb oraz wybieram Database Connection z listy rozwijalnej, która wskazuje na bazę danych piodb.

Ciekawostką tego Create Persistence Unit w porównaniu z tym, które napotkałbym w Java Class Library jest sposób pobrania źródeł danych dla PU - w pierwszym (obecnie wykorzystywanym) przypadku będzie to lista źródeł z serwera aplikacyjnego, podczas, gdy w drugim przypadku będzie to lista zdefiniowana w NetBeans IDE w zakładce Services. Zawsze mi tego brakowało i nie jestem pewien, kiedy pojawiło się to rozróżnienie w NetBeans.

Dodatkowo wybieram opcję Drop and Create.


Definicję jednostki trwałej zatwierdzam przyciskiem Create.

Przyciskiem Next > przechodzę do kolejnego etapu asystenta JSF Pages from Entity Class. Przyciskiem Browse... wybieram katalog docelowy tworzonych stron JSF (pole JSF Pages Folder) i jako Package podaję pl.jaceklaskowski.pio.faces.


Zatwierdzam przyciskiem Finish.

I tu niespodzianka. Mimo, że wybrałem pakiet pl.jaceklaskowski.pio.faces jako pakiet klas JSF to i tak ostatecznie asystent umieścił niektóre z nich w pakiecie pioweb i dodatkowo utworzył puste pakiety pioweb.odpowiedz oraz pioweb.pytanie. Zgłosiłem jako 130861: JSF Pages from Entity Class generates classes in package as project name oraz 130862: JSF Pages from Entity Class wizard creates empty packages. I na koniec jeszcze jeden błąd, gdzie klasy tworzone są w pakiecie "krótszym" od podanego, tj. pl.jaceklaskowski.pio, który okazał się być pojedyńczym katalogiem o nazwie pl.jaceklaskowski.pio.faces - 130863: JSF Pages from Entity Class generates classes in a "shorter" package that's a single directory.

Rozwiązaniem tych niespodzianek jest zaniechanie podania własnego katalogu dla tworzonych klas bądź utworzenie pakietu własnoręcznie i przeniesienie do niego klas. Wybieram podejście drugie i przeniosę klasy do pakietu pioweb (refaktoryzacja Move Classes). I tu kolejna niespodzianka - 130864: NPE upon Move Classes.

Nie pozostaje nic innego jak skasować projekt PioWeb i stworzyć go od nowa, bez specyfikowania pakietu.

UWAGA: Czasami skasowanie projektu nie kasuje jego katalogu mimo zaznaczenia opcji Also Delete Sources Under...
I tutaj jeszcze kolejna niespodzianka - podczas tworzenia projektu o tej samej nazwie otrzymałem zbiór wjątków NPE oraz IAE - 130865: NPE and IAE upon creating a visual jsf web project after it's been deleted. Mimo wszystko projekt się utworzył. Zamknąłem jednak NetBeans IDE 6.1 i otworzyłem go ponownie wcześniej kasując projekt PioWeb z poziomu Exploratora Windows. Tym razem obyło się bez niespodzianek.

Pora na uruchomienie aplikacji webowej PioWeb. Wybieram menu kontekstowe Run.


Projekt otworzy domyślną stronę startową aplikacji, więc nie ma zaskoczenia, kiedy pojawi się pusta strona w przglądarce. Przechodzimy do strony, gdzie utworzymy kilka nowych odpowiedzi - http://localhost:8080/pio/faces/odpowiedz/New.jsp.


Wybranie akcji Create kończy się jednakże błędem, który okazuje się być związany z niepełną konfiguracją JPA - brakiem wskazania klas encji w PU.


Wracam do NetBeans, gdzie do pliku persistence.xml w projekcie PioWeb dodaję klasy encji. Ten błąd wynika z faktu, że encje są w innym projekcie, więc niekoniecznie klasyfikuję to jako błąd w NetBeans.


Po zmianie wybieram menu kontekstowe Undeploy and Deploy i ponownie tworzę odpowiedź.


Teraz pora na nowe pytania, czyli przechodzę do strony http://localhost:8080/pio/faces/pytanie/New.jsp.

Myliłby się ten, kto uważałby, że tutaj pójdzie gładko ;-) Na konsoli GF pojawi się następujący błąd:
Caused by: javax.el.PropertyNotFoundException: The class 'pioweb.PytanieController' does not have the property 'odpowiedziOfPytanie'.
at javax.el.BeanELResolver.getBeanProperty(BeanELResolver.java:547)
at javax.el.BeanELResolver.getValue(BeanELResolver.java:249)
at javax.el.CompositeELResolver.getValue(CompositeELResolver.java:143)
at com.sun.faces.el.FacesCompositeELResolver.getValue(FacesCompositeELResolver.java:64)
at com.sun.el.parser.AstValue.getValue(AstValue.java:138)
at com.sun.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:206)
at javax.faces.component.UIOutput.getValue(UIOutput.java:173)
... 57 more
Zgłaszam kolejny błąd do bazy zgłoszeń NB - 130867: PNFE : The class 'pioweb.PytanieController' does not have the property 'odpowiedziOfPytanie'.

Rozwiązanie to dodanie atrybutu odpowiedziOfPytanie do klasy ziarna pioweb.PytanieController (pole private List<Odpowiedz> odpowiedziOfPytanie z metodami set i get). Ponownie Undeploy and Deploy, utworzenie odpowiedzi i podejście do utworzenia pytania.


Wstrzymanie oddechu, wciśnięcie Create i...

...tym razem udało się - pytanie utworzone! Nie było łatwo, ale wierzę, że przed produkcyjną wersją NetBeans IDE 6.1 wszystkie zgłoszone problemy zostaną rozwiązane. Fajnie jest mieć możliwość szybkiego utworzenia w pełni działającej aplikacji opartej o JPA i JSF, więc już nie mogę doczekać się pozamykania zgłoszeń i ogłoszeniu, że to, co znamy z Ruby on Rails czy JBoss Seam mamy i w NetBeans IDE 6.1 (nie pamiętam, czy przypadkiem podobnej funkcjonalności nie ma już w Eclipse czy jego rozbudowanym krewnym IBM Rational Application Developer 7.5 BETA).

Pytanie na zakończenie dla wytrwałych czytelników, którzy mają przyjemność czytać to: Dlaczego w polu Odpowiedzi pojawiło się pl.jaceklaskowski.pio.encja.Odpowiedz[id=1] zamiast treści odpowiedzi? Na szczęśliwych zwycięzców nie czekają nagrody.

Aplikacja Pytania i Odpowiedzi (PiO) w postaci projektów NetBeans dostępna jest jako pio-netbeansprojects.zip.