12 czerwca 2009

OpenEJB 3.1.x, EJB 3.1, @ApplicationException oraz maven.surefire.debug

Pewnie zastanawiasz się, skąd ta różnorodność tematyczna w temacie, co? Dla mnie też było to niemałym zaskoczeniem, kiedy uzmysłowiłem sobie, ile rozwiązań przewinęło mi się dzisiaj, aby rozwiązać wydawałoby się początkowo banalne zgłoszenie OPENEJB-980 @ApplicationException inheritance. Najpierw sądziłem, że to kwestia prześwietlenia dostępnych klas w poszukiwaniu adnotacji @ApplicationException. I tutaj pierwsze bliższe spotkanie z ASM, który wykorzystywany jest w Apache XBean do prześwietlania klas bez konieczności ładowania ich do pamięci JVM z użyciem Reflection API. Wystarczy uruchomić ASM, który poprzez wykorzystanie wzorca projektowego Visitor pozwala na gromadzenie informacji o przetwarzanej klasie. W ten sposób do pamięci PermGen, gdzie trzymane są wszystkie definicje wczytanych klas, nie trafiają definicje, które nie są w użyciu i nie puchnie ona niepotrzebnie.

Miałem już listę klas, które są oznaczone adnotacją @ApplicationException oraz ich potomków. Przyszła więc pora na obsługę samego wyjątku, który niezgodnie z zasadami mechanizmu adnotacji w Javie powinien być oznaczony przez @java.lang.annotation.Inherited, gdyby miał być faktycznie dziedziczony (zdanie w 3. trybie warunkowym z opuszczeniem "if" możecie znaleźć w komentarzu Davida Blevinsa do zgłoszenia - "Had the annotation intended to be inherited it would have been annotated with @Inherited which causes the annotation to be propagated to all the subclasses as described"). W rzeczywistości jednak taka zmiana trafiła do specyfikacji EJB 3.1, w której, mimo, że adnotacja !ApplicationException nie jest @Inherited może taką być przez element inherited z wartością logiczną (domyślnie jest true, czyli jest propagowany w dół hierarchii dziedziczenia). W swoim komentarzu, Dave zaprezentował krótki przykład, który pokazuje, gdzie jest problem (początkowo miałem problemy z dokładnym zrozumieniem tematu, aż do tej chwili, kiedy pojawił się ten przykład - może i Tobie się przyda, o czym ja).

Zajrzałem do specyfikacji EJB 3.1 PFD (ang. Proposed Final Draft) z 24. lutego 2009, rozdział "14.2.1 Application Exceptions" (str. 378). W EJB 3.1, przypisanie wyjątku kontrolowanego (ang. checked exception) jako wyjątku aplikacyjnego dotyczy również wszystkich wyjatków pochodnych. Wystarczy jednak skorzystać z elementu inherited z wartością false, aby dziedziczenie wyłączyć (alternatywnie można użyć elementu application-exception w deskryptorze XML). Zastanawiam się skąd ta decyzja. Pewnie nadanie @Inherited do adnotacji @ApplicationException złamałoby kontrakt, a dodanie elementu już nie (?)

Na stronie 380 specyfikacji EJB 3.1 PFD pokazany jest przykład z wyjątkiem aplikacyjnym dziedziczonym (ExceptionA) oraz nie (ExceptionC). Kolejne miejsce do nauki przez przykład. Jest krótki, więc zdecydowanie nie ma się nad czym zastanawiać, tylko zapoznać się z nim.

Wprowadziłem odpowiednie zmiany w Apache OpenEJB, więc kolejne wersje będą to honorowały (na razie ignorowana jest wartość "inherited" i wszystko jest tak, jakby było "inherited=true"). Jako gotowy przykład do uruchomienia proponuję zajrzeć do aplikacji demonstracyjnej towarzyszącej zmianie applicationexception. Mając klienta Subversion oraz Apache Maven 2, wystarczy
 svn co https://svn.apache.org/repos/asf/openejb/trunk/openejb3/examples/applicationexception
cd applicationexception
mvn test
UWAGA: Zmiany potrzebne do uruchomienia przykładu nie są jeszcze rozpropagowane po repozytoriach mavenowych, więc z pewnością zakończy się BUILD FAILURE. Dodatkowo, początkowe uruchomienie pobiera wiele (chyba zdecydowanie za wiele) zależności ze zdalnych repozytoriów, więc przygotuj się na dłuższą przerwę. Najlepiej po prostu ściągnąć źródła OpenEJB i give it a try.

Pozostaje jeszcze wyjaśnić maven.surefire.debug z tytułu wpisu. Pisząc test jednostkowy do tej zmiany użyłem adnotacji @Test(expected = ValueRequiredException.class), która instruuje JUnit, że w teście oczekuje się zgłoszenia wyjątku ValueRequiredException. Początkowo nie wiedziałem, dlaczego ta zmiana to za mało i każdorazowe uruchomienie Mavena kończyły się BUILD ERROR. Doprowadzało mnie to do szału, aż znalazłem rozwiązanie - wyrzucenie "extends TestCase" z definicji klasy testowej ThrowBusinessExceptionImplTest (!) Jeszcze uaktualniłem JUnit do wersji 4.6 oraz maven-surefire-plugin do 2.4.2 i wszystko zaczęło działać poprawnie. W międzyczasie rozglądałem się za dostępnymi, specjalnymi ustawieniami w Mavenie, które mogłyby mi pomóc (wiedziałem, że to może być coś związanego z Mavenem, albo samym testem, ale nie wiedziałem dokładnie co), aż natrafiłem na stronę Debugging Tests. Zamiast definiować konfigurację śledzenia (debugowania) testu w pom.xml wystarczy skorzystać z parametru maven.surefire.debug. Test jest zatrzymywany i oczekuje na podłączenie klienta JDWP...ekhm...zdalnego debuggera. Można również skorzystać z mvnDebug i parametru forkMode, o czym również nie wiedziałem. Do tej pory, kopiowałem konfigurację uruchomienia z włączonym debug do wtyczki maven-surefire-plugin w pom.xml, a tu proszę, rozwiązanie gotowe i pod ręką.

Ze zgłoszenia OPENEJB-980 pozostaje mi jeszcze obsłużyć element inherited w @ApplicationException i temat będzie całkowicie zamknięty. Uwagi mile widziane i pożądane. Nie jest to zmiana najwyższych lotów, a wpisy Łukasza Lenarta Jak cię widzą, tak cię piszą czy Pawła Szulca Testing and Testable Code Presentation tylko mnie utwierdziły, że jeszcze wiele przede mną w temacie dobrych testów jednostkowych.

Z ciekawostek na koniec - podczas testowania przykładu z pustym repozytorium mavenowym zauważyłem, że Apache Maven 2.1.0 pobiera zależności...równolegle (chyba tylko tak mogę interpretować, co zauważyłem - oznaczone na czerwono poniżej). A może jednak nie? To co miałoby to być?!