03 listopada 2008

@Resource UserTransaction w EJB 3.0 i poprawka dla OpenEJB

Pojawiła się kolejna wersja Apache OpenEJB 3.1 z obsługą EJB 3.1. Tak, tak, my tu o EJB 3.0, a na horyzoncie już się pojawił EJB 3.1 w OpenEJB 3.1 oraz GlassFish v3 Prelude. Na fali wydania postanowiłem obsłużyć jedno ze zgłoszeń dotyczących kontroli poprawności ziaren EJB w kontekście użycia UserTransaction - OPENEJB-847 Validation: @Resource UserTransaction injection mistakenly used on bean with Container-Managed Transactions. Zgłoszenie OPENEJB-847 dotyczyło sprawdzenia, czy deklaracja dostępu do zasobu UserTransaction ma miejsce jedynie w ziarnie EJB z samodzielnie zarządzanymi transakcjami. Zgodnie ze specyfikacją EJB 3.0, rozdział 16.12 UserTransaction Interface (strona 448) dostęp do UserTransaction jest możliwy wyłącznie dla ziaren sesyjnych (stanowych i bezstanowych) oraz komunikacyjnych (sterowanych komunikatami) z zarządzaniem transakcjami przez nie same (ang. BMTD - bean-managed transaction demarcation).

W najczęściej spotykanych przypadkach, transakcyjność ziaren EJB zarządzana jest przez kontener i w takim przypadku nie mamy prawa skorzystać z interfejsu UserTransaction wprost, czy to przez adnotację @Resource UserTransaction, czy deklarując go w deskryptorze wdrożenia (META-INF/ejb-jar.xml). Po prostu, kontrola zasięgu transakcji odbywa się deklaratywnie na poziomie metody lub całej klasy za pomocą adnotacji TransactionAttribute (domyślnie REQUIRED) z TransactionManagement ustawionym na CONTAINER (wartość domyślna).

Pod wpływem niekończących się dyskusji o bardziej przyjaznej IntelliJ IDEA do tworzenia aplikacji javowych (chociażby Fwd: JetBrains Giveaway Program Update), do napisania poprawki dla OpenEJB postanowiłem właśnie z niej skorzystać i przekonać się o tym na własnej skórze. Pamiętam czasy wersji 3 i później Ariadnę (nazwa kodowa dla wersji 4.0), i faktycznie coś w niej było. Później jeszcze kilkakrotnie przymierzałem się do niej, ale nie starczyło zacięcia. Tym razem zawziąłem się. Rozpocząłem od uruchomienia IntelliJ IDEA 8.0M1 i zaimportowałem projekt dzięki wsparciu dla projektów mavenowych. Po ostatniej lekturze artykułu "Poznaj JUnita" (Refleksje po "Poznaj JUnit 4" z Internet MAKER 5/08) tylko czekałem chwili, aby zmierzyć się z tematem tworzenia oprogramowania począwszy od testów jednostkowych. Trochę trwało, zanim faktycznie dałem się przekonać, że właśnie od testu powinienem zacząć i ruszyłem z miejsca, gdyż samo zestawienie całego "środowiska" do wykonania poprawki nie należało do najtrywialniejszych zadań (przede wszystkim utworzenie reprezentacji obiektowej konfiguracji ziarna EJB operając się na klasach JAXB). Szczęśliwie dzisiaj nie trwało to na tyle długo, abym się zniechęcił i po chwili miałem swoją wymarzoną, działającą poprawkę (!) Nadeszła upragniona pora, aby ją sprawdzić poza testem jednostkowym. Skorzystałem z pomocy NetBeans IDE 6.5 RC2 (który właśnie niedawno został wydany - do ostatniego wydania pozostało już kilka tygodni) i stworzyłem przykładowe ziarno bezstanowe z adnotacją @Resource.
 package nopackage;

import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.transaction.UserTransaction;

@Stateless
public class CheckUserTransactionRefsTestBean implements CheckUserTransactionRefsTestLocal {

@Resource
UserTransaction tx;

public String businessMethod() {
String result;
try {
result = "tx is null? " + (tx == null);
} catch (Exception ex) {
result = ex.getMessage();
}
return result;
}
}
Jakież było moje zdumienie, kiedy podczas jego uruchomienia na GlassFish V2 otrzymałem komunikat:

Ale jak to?! False?! Czyż @Resource UserTransaction nie jest dedykowane wyłącznie dla ziaren sesyjnych i MDB z samodzielnym zarządzaniem transakcji?! Taką miałem wiedzę, kiedy tworzyłem moją poprawkę dla OpenEJB i tego spodziewałem się w GlassFish (!) W tym momencie, przypomniałem sobie, jak podczas mojego ostatniego wystąpienia z tematem EJB 3.0 podczas NetBeans Day 2008 w Gdańsku, właśnie wykorzystanie transakcji zakończyło się porażką, gdyż...wykonałem przynajmniej jedną metodę na przekazanym egzemplarzu UserTransaction (cóż za zbieg okoliczności - najpierw NetBeans Day, później artykuł o JUnit, a teraz okazja do zastosowania wiedzy praktycznie w OpenEJB). Zmieniłem kod ziarna na następujący
 result = "tx is null? " + (tx == null) + " ze statusem " + tx.getStatus();
i...jest wyjątek!

Przyszła więcej pora na testy w środowisku OpenEJB. Najpierw sprawdzenie, jakie będzie działanie przykładowego ziarna w ramach Apache Geronimo 2.1.2, który jest serwerem aplikacyjnym Java EE 5 z kontenerem EJB opartym na OpenEJB. I tu bez niespodzianek - działa i to zgodnie z oczekiwaniami - UserTransaction jest puste. Odetchnąłem z ulgą. Teraz było już z górki - upragnione zatwierdzenie zmian (commit). Zainteresowanych szczegółami implementacji zapraszam do lektury Subversion Commits dla tego zgłoszenia. Nie ma tego wiele, więc pełne rozpoznanie tematu powinno zająć niespełna kwadrans.

p.s. Zauważyłem wiele podobieństw między NetBeans IDE 6.5 a IntelliJ IDEA 8.0M1, gdzie okienko tworzenia nowych elementów klasy, w NetBeans - Ctrl+Insert istnieje w IDEA jako...Alt+Insert oraz sout+TAB jako szablon dla System.out.println("")...również. Gdzieś już trafiłem na podobną refleksję (nie był to Radek Holewa?!). Już te dają mi do myślenia, a pewnie jest więcej.

3 komentarze:

  1. "w NetBeans - Ctrl+Insert istnieje w IDEA jako...Alt+Insert"

    W moim Netbeansie 6.5 działa Alt+Insert - chyba coś przestawiłeś w mapowaniu klawiszy? Bo ja na pewno nie :)

    A tak a propos to w sumie chyba nie jest to zbyt dobre miejsce - trzeba sporą drogę przebyć prawą ręką ;) To szczególnie uporczywe dla Emacsowców ;)

    OdpowiedzUsuń
  2. Zastanawiam sie czemu w CheckUserTransactionRefsTest stosujesz javowe assercje zamiast statycznych metod z klasy Assert JUnita. Ma to jakies uzusadnienie? Jesli Twoje testy zostana odpalone w srodowisku, gdzie sprawdzenie assercji nie zostalo wlaczone (-ea) test przejdzie mimo niespelnionych warunkow.

    OdpowiedzUsuń
  3. @lechique, zastosowanie assert to jedynie przyzwyczajenie (trudno znaleźć mi dla tego lepsze/inne uzasadnienie). Po tym, kiedy przeczytałem o assertThat w JUnit 4.x w ostatnim Internet MAKER pewnie już nie wrócę do żadnego innego assert*.

    p.s. Nie zwróciłem uwagi, że faktycznie wyłącznie -ea asserty "przejdą". Coś mi mówi, że mvn test włącza tę opcję. A tak przy okazji, to właśnie włączanie/wyłączanie assertów jest powodem, dla którego nie może być wykorzystywane w metodach nieprywatnych do kontroli parametrów (jedno z pytań na SCJP5+).

    OdpowiedzUsuń