30 czerwca 2008

@DataModel, @DataModelSelection oraz @Factory w JBoss Seam

1 komentarzy
Jakiś czas temu podczas rozpoznawania JavaServer Faces (JSF) i źródeł danych dla kontrolki h:dataTable pisałem o strukturze danych javax.faces.model.DataModel - Tabele w JavaServer Faces - znacznik <h:dataTable>. Za pomocą tej struktury istnieje możliwość dostarczania danych tabelarycznych i w prosty sposób otrzymywać informację zwrotną o wybranym przez użytkownika wierszu. Nie potrzebne jest przechwytywanie identyfikatora wiersza czy danych w nim zawartych, a wystarczy jedynie związać h:dataTable ze strukturą DataModel i temat obsługi tabeli mamy załatwiony. Skoro JBoss Seam opiera swoje działanie na JSF możnaby oczekiwać podobnej funkcjonalności, potencjalnie uproszczonej w świetle wielu uproszczeń dostarczanych przez Seama. I nie mylił się ten, kto tego oczekiwał (wprowadzenie jak z okładki Seama - taka radość płynie z tego wstępu ;-)).

JBoss Seam dostarcza adnotację @DataModel, która oznacza kolekcję danych (listę, mapę, zbiór, tablicę) jako typ odpowiadający javax.faces.model.DataModel. Dodatkową adnotacją @DataModelSelection określamy pole egzemplarza jako przechowujące wybór użytkownika w h:dataTable i ponownie temat obsłużony. Niewiele uproszczeń dostarczają obie adnotacje, ale w dobie adnotacji programowanie bez nich stało się jakieś takie niemodne.

Przykład opisany w dokumentacji JBoss Seam 2.0.3.CR1 dotyczący tematu @DataModel to 1.3. Clickable lists in Seam: the messages example i większość z mojego przykładu była na nim wzorowana z akompaniamentem książki Beginning JBoss® Seam: From Novice to Professional wydawnictwa Apress.

Zacznijmy od prezentacji komponentu kategoriaAgent reprezentowanego klasą pl.jaceklaskowski.seam.KategoriaAgent:
 package pl.jaceklaskowski.seam;

import java.util.ArrayList;
import java.util.List;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Factory;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.datamodel.DataModel;
import org.jboss.seam.annotations.datamodel.DataModelSelection;
import org.jboss.seam.log.Log;

@Name("kategoriaAgent")
@Scope(ScopeType.SESSION)
public class KategoriaAgent {

@In
private Kategoria kategoria;

@DataModel
private List<Kategoria> kategorie = new ArrayList<Kategoria>();

@DataModelSelection
@Out(required = false)
private Kategoria kategoriaWybrana;

@Logger
private Log log;

public void dodaj() {
log.info("Zapis kategorii \"#{kategoria.nazwa}\"");
kategorie.add(kategoria);
}

public void wybierz() {
log.info("Wybrano kategorię \"#{kategoriaWybrana.nazwa}\"");
}

public void skasuj() {
log.info("Kasowanie kategorii \"#{kategoriaWybrana.nazwa}\"");
kategorie.remove(kategoriaWybrana);
kategoriaWybrana = null;
}

@Factory("kategorie")
public void pobierzKategorie() {

log.info("Wykonanie metody pobierzKategorie()");

Kategoria rodzic = new Kategoria("Rodzic", "Kategoria nadrzędna", null);
Kategoria potomek = new Kategoria("Potomek", "Kategoria podrzędna", rodzic);
kategorie.add(rodzic);
kategorie.add(potomek);
}

}
Na uwagę zasługuje miejsce "przyłożenia" adnotacji @DataModel oraz @DataModelSelection. W zasadzie nie ma co opisywać, po co i dlaczego, mając na uwadze ich działanie (warto zapoznać się z ich dokumentacją javadoc dla pełnego obrazu).

Jako adnotację wspierającą należy wskazać @Factory, która wskazuje metodę będącą fabryką danych dla struktury o nazwie wskazanej jako jej wartość (w moim przykładzie będzie to pole egzemplarza o nazwie kategorie).

Najważniejszą zmianą, która powoduje, że pobranie danych nie następuje przy każdym żądaniu jest @Scope(ScopeType.SESSION). Dzięki tej adnotacji określamy, że zakres dostępności komponentu kategoriaAgent to czas sesji, więc wykonanie metody pobierzKategorie() będzie następowało podczas pierwszego pobrania danych do tabeli w sesji i tak długo, jak sesja będzie aktywna tak długo nie nastąpi kolejne wywołanie tej metody.

Dodając do tego użycie adnotacji @Out przy polu kategoriaWybrana określa udostępnienie wyboru klienta "światu zewnętrznemu", tj. nastąpi utworzenie zmiennej kategoriaWybrana o zasięgu EVENT (równoznaczne z zasięgiem żądania).

Pozostaje zaprezentować stronę XHTML, w której skorzystam z utworzonych struktur danych - kategoria.xhtml:
 <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<head>
<title>Administracja kategoriami</title>
</head>
<body>
<f:view>
<h:form>
<s:validateAll>
<h:panelGrid columns="2">
Nazwa: <h:inputText value="#{kategoria.nazwa}" required="true" />
Opis: <h:inputText value="#{kategoria.opis}" required="true" />
</h:panelGrid>
</s:validateAll>
<h:messages />
<h:commandButton value="Dodaj" action="#{kategoriaAgent.dodaj}" />
<br />
<h:outputText value="Brak kategorii" rendered="#{kategorie.rowCount==0}" />
<h:dataTable var="ktgria" value="#{kategorie}" rendered="#{kategorie.rowCount>0}">
<h:column>
<f:facet name="header">
<h:outputText value="Nazwa" />
</f:facet>
<h:commandLink value="#{ktgria.nazwa}" action="#{kategoriaAgent.wybierz}" />
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Opis" />
</f:facet>
<h:outputText value="#{ktgria.opis}" />
</h:column>
<h:column>
<h:commandButton value="Delete" action="#{kategoriaAgent.skasuj}" />
</h:column>
</h:dataTable>
<h3><h:outputText value="#{kategoriaWybrana.nazwa}" /></h3>
<div><h:outputText value="#{kategoriaWybrana.opis}" /></div>
</h:form>
</f:view>
</body>
</html>
Wdrożenie aplikacji na serwer aplikacyjny Apache Geronimo i uruchomienie strony http://localhost:8080/seam-richfaces-tree/seam/kategoria.xhtml:


a na konsoli Geronimo:
 23:43:30,187 INFO  [KategoriaAgent] Wykonanie metody findMessages()
Zatwierdzenie formularza z danymi nowej kategorii i kolejne komunikaty aplikacji na konsoli Geronimo:
 23:45:34,546 INFO  [Version] Hibernate Validator 3.0.0.GA
23:45:34,671 INFO [KategoriaAgent] Zapis kategorii "Nowa kategoria"
Nazwa jest aktywnym odnośnikiem do akcji na serwerze i wybranie dowolnej skutkuje kolejnymi komunikatami:
 23:46:52,781 INFO  [KategoriaAgent] Wybrano kategorię ""
Właśnie! Dlaczego zawsze w komunikacie mam poprzednią wartość #{kategoriaWybrana.nazwa}? Przez "poprzednią" dla pierwszego wywołania będzie to wartość pusta, a dla kolejnych...hmmm...właśnie poprzednia.


Pytanie konkursowe: Jakie 2 adnotacje upraszczają pracę z h:dataTable w komponentach seamowych?

28 czerwca 2008

EJB 3.0 - Rozdział 5. Ziarno sterowane komunikatami MDB

0 komentarzy
Już dawno nie zaglądałem do specyfikacji EJB 3.0. Ostatni wpis dotyczący stricte lektury specyfikacji EJB 3.0 dotyczył mechanizmów bezpieczeństwa - EJB 3.0 - Rozdział 17: Mechanizm bezpieczeństwa z datą 24 lutego 2008. Ostatnimi jednak czasy pracowałem dosyć intensywnie z IBM WebSphere Application Server 6.1 i jego funkcjonalnością związaną z komunikacją JMS realizowaną przez Service Integration Bus (SIBus). Jednym z wyzwań, jakie przyszło mi podjąć, było zestawienie środowiska IBM WebSphere Portal 6.0 z IBM WebSphere Process Server 6.1, których wykonanie usług sieciowych (ang. web services) realizowane było za pomocą protokołu SOAP "puszczonego" przez JMS (zamiast HTTP), gdzie szyną komunikacyjną był IBM WebSphere MQ 6.0. Dużo dobrej zabawy i z 3 zaplanowanych dni zeszło się bodajże do tygodnia, albo nawet i dłużej. Ostatecznie klient usługi sieciowej uruchomiony po stronie Portala wywołał usługę sieciową po stronie Process Servera za pomocą komunikatu SOAP przesłanego przez JMS i radość zapanowała ogromna ;-)

W trakcie konfiguracji dotknąłem pewnego aspektu serwera aplikacyjnego określanego mianem specyfikacja aktywacji (ang. activation specification), która definiuje sposób wzbudzenia ziarna sterowanego komunikatami (dalej: ziarno MDB - Message-Driven Bean lub ziarno komunikacyjne). Kiedy jednak zadano mi pytanie co to jest dokładnie specyfikacja aktywacji niewiele mogłem powiedzieć, poza kilkoma mętnymi zdaniami (coś ala eee..uuu...no wiesz...i takie tam). Wtedy właśnie zdecydowałem się na powrót do specyfikacji i rozpoznania od podszewki, co i jak w trawie piszczy w kontekście ziaren MDB. I oto jest - relacja z rozdziału 5. Kontrakt ziarna sterowanego komunikatami (Chapter 5 Message-Driven Bean Component Contract) specyfikacji Enterprise JavaBeans 3.0.

Przykładowa aplikacji korporacyjna korzystająca z ziarna MDB oraz JPA uruchomiona w środowisku Apache Geronimo jest opisana w moim artykule Aplikacja Java EE 5 z MDB z JPA w trybie JTA i PostgreSQL w Apache Geronimo 2.

Rozdział 5 opisuje kontrakt ziarna MDB - jego prawa i obowiązki oraz środowisko serwera aplikacyjnego (kontenera EJB).

Ziarna MDB nie posiadają interfejsu klienckiego i w związku z tym są dla klienta niewidoczne, tj. nie można wywołać ziarna MDB bezpośrednio.

Ziarna MDB nie posiadają stanu konwersacyjnego (stan będący wynikiem wielu wywołań), i w tym zakresie można je porównać do bezstanowych ziaren sesyjnych (SLSB). Podobnie jak SLSB, ziarna MDB mogą jednakże przechowywać pewien stan w zmiennych instancji, aczkolwiek jest on identyczny dla wszystkich egzemplarzy MDB. Stąd też wszystkie egzemplarze MDB są identyczne, poza czasem obsługi komunikatów. I tu pojawia się sedno istnienia ziaren MDB - asynchroniczna obsługa komunikatów. Cykl rozwojowy kontrolowany jest całkowicie przez kontener.

Celem udostępnienia MDB w specyfikacji EJB 3.0 jest umożliwienie tworzenia ziarna korporacyjnego, które jest asynchroniczne wywoływane w celu przetwarzania nadchodzących komunikatów, w sposób nie bardziej skomplikowany niż jak to ma miejsce przy konstrukcji dowolnego komponentu nasłuchującego komunikatów. W ten sposób istnieje możliwość równoległego przetwarzania strumienia komunikatów.

W zasadzie nie istnieje pojęcie klienta MDB, gdyż dostęp do ziarna MDB jest wyłącznie poprzez wysłanie komunikatu pod adres przeznaczenia (ang. destination) lub punkt końcowy (ang. endpoint), pod którym nasłuchuje MDB (podobnie jak inni słuchacze, niekoniecznie ziarna MDB).

Obiekty reprezentujące adres przeznaczenia komunikatów - kolejki (ang. queue) lub tematu (ang. topic) - mogą zostać przekazane przez kontener poprzez mechanizm wstrzeliwania zależności (ang. DI - dependency injection) lub odszukane w przestrzeni nazewniczej (drzewie) JNDI klienta.

Skorzystanie z mechanizmu DI sprowadza się do udekorowania pola egzemplarza adnotacją @Resource:
 @Resource Queue stockInfoQueue;
co równoznaczne jest z odszukaniem w drzewie JNDI (obowiązkowo należy związać jms/stockInfoQueue z odpowiednim, fizycznym bytem dostępnym na serwerze):
 Context initialContext = new InitialContext();
Queue stockInfoQueue = (javax.jms.Queue)initialContext.lookup("java:comp/env/jms/stockInfoQueue");
Egzemplarz MDB żyje w ramach serwera aplikacji (kontenera EJB), który dostarcza wszystkie usługi konieczne do poprawnej pracy MDB: bezpieczeństwo, współbieżność, transakcje, itp. Dostarczenie komunikatu klienta następuje do dowolnego egzemplarza ziarna MDB.

Za pomocą adnotacji @MessageDriven lub elementu message-driven w deskryptorze wdrożenia ejb-jar.xml definiuje się klasę będącą ziarnem MDB. Klasa musi implementować odpowiedni interfejs komunikacyjny, np. javax.jms.MessageListener dla obowiązkowego wsparcia JMS (Java Message Service) lub określić jego realizację elementem messaging-type w deskryptorze wdrożenia ejb-jar.xml. Właściwa dla interfejsu komunikacyjnego metoda, np. onMessage() w przypadku javax.jms.MessageListener, zostanie wywołana w momencie otrzymania komunikatu do obsłużenia.

Jeśli klasa ziarna MDB implementuje więcej niż jeden interfejs inny niż java.io.Serializable, java.io.Externalizable czy dowolny interfejs z pakietu javax.ejb, interfejs komunikacyjny musi być określony atrybutem messageListenerInterface adnotacji @MessageDriven lub atrybutem messaging-type elementu message-driven w deskryptorze wdrożenia.

Ziarno MDB korzysta z całego zakresu mechanizmu wstrzeliwania zależności oferowanego przez kontener EJB. Wstrzeliwanie zależności następuje po utworzeniu klasy ziarna, a przed wykonaniem metody komunikacyjnej.

Dostęp do kontekstu wykonania ziarna następuje poprzez mechanizm DI w postaci adnotacji @Resource na polu typu javax.ejb.MessageDrivenContext lub implementację interfejsu javax.ejb.MessageDrivenBean. W ten sposób ziarno MDB w trybie zarządzania transakcjami przez serwer (CMTD - container-managed transaction demarcation) może oznaczyć bieżącą transakcję jako wyłącznie do wycofania - metoda setRollbackOnly() - czy sprawdzić jej stan - metoda getRollbackOnly(). W trybie samodzielnego zarządzania transakcjami (BMTD - bean-managed transaction demarcation) ziarno MDB korzysta z metody getUserTransaction() do uzyskania dostępu do mechanizmu zarządzania transakcjami przez interfejs javax.transaction.UserTransaction. Bez względu na tryb transakcyjny ziarna MDB można skorzystać z metod:
  • getTimerService() - dostęp do mechanizmu budzika (ang. timer service);
  • getCallerPrincipal() - dostęp do osobowości (java.security.Principal) związanej z bieżącym wywołaniem ziarna;
  • lookup() - metoda dostępu do prywatnego drzewa JNDI (z którego pobierane są obiekty przez mechanizm DI);
Metody isCallerInRole(), getEJBHome() oraz getEJBLocalHome() są oddziedziczone z interfejsu javax.ejb.EJBContext, których wykonanie jest zabronione przez ziarno MDB.

Możliwe jest związanie wykonania ziarna MDB z interceptorami poprzez określenie metod przechwytujących bezpośrednio w ziarnie MDB lub pośrednio w związanej klasie interceptora:
  • Adnotacja @PostConstruct określa metodę wykonywaną przed wykonaniem metody komunikacyjnej a po wykonaniu DI w nieokreślonym kontekście bezpieczeństwa i transakcji;
  • Adnotacja @PreDestroy określa metodę wykonywana przed usunięciem ziarna z puli bądź zniszczeniem w nieokreślonym kontekście bezpieczeństwa i transakcji.
Metody przechwytujące oznaczone @PrePassivate lub @PostActivate są ignorowane.

Ziarno MDB nie jest zobowiązane do implementowania interfejsu javax.ejb.MessageDrivenBean. Jest to zaszłość z poprzednich wersji specyfikacji EJB. Dostęp do zasobów oferowanych ziarnom MDB realizującym ten interfejs jest możliwa przez skorzystanie z mechanizmu DI oraz metod przechwytujących (interceptorów). Interfejs oferuje dwie metody:
  • setMessageDrivenContext() - przekazanie kontekstu wykonania MDB (analogiczna do @Resource MessageDrivenContext);
  • ejbRemove() - wykonana przed zniszczeniem egzemplarza ziarna MDB (analogiczna do @PreDestroy)
Na uwagę zasługuje wymaganie, które nakłada na kontener EJB obowiązek określenia metody ejbRemove() jako wyłączną metodę przechwytującą @PreDestroy (alternatywnie przez deskryptor wdrożenia) w przypadku implementacji interfejsu javax.ejb.MessageDrivenBean.

Ziarno MDB może zostać zarejestrowane jako odbiorca zdarzenia wzbudzenia budzika.

Utworzenie ziarna MDB przez kontener następuje trzykrokowo:
  1. Wykonanie metody newInstance()
  2. Przekazanie kontekstu wykonania jako egzemplarz typu MessageDrivenContext, jeśli wymagane oraz wstrzelenie zależności
  3. Wykonanie @PostConstruct, jeśli istnieje (jeśli istnieje metoda ejbCreate() to jest ona traktowana jako metoda @PostConstruct)
Można zdefiniować interceptor AroundInvoke dla ziarna MDB bezpośrednio w ramach ziarna lub pośrednio poprzez klasę interceptora.

Kontener szereguje wywołania metod ziarna MDB zdejmując troskę o współbieżność z programisty. Wykonanie metod ziarna, interceptory czy metody budzika są wszystkie wykonywane szeregowo (pojedyńczo, jedna po drugiej).

Nie ma gwarancji przetwarzania komunikatów w kolejności ich nadawania. Obsługa komunikatów odbywa się równolegle przez wiele egzemplarzy ziarna MDB.

Metoda komunikacyjna ziarna MDB oraz metoda budzika wykonywane są w kontekście transakcji określonym przez adnotacje lub deskryptor wdrożenia ejb-jar.xml. Dla ziarna MDB w trybie CMTD metoda komunikacyjna może być oznaczona przez atrybuty REQUIRED lub NOT_SUPPORTED, a metoda budzika przez REQUIRED, REQUIRES_NEW lub NOT_SUPPORTED. Ziarno MDB w trybie BMTD korzysta z javax.transaction.UserTransaction do określenia zasięgu trwania transakcji. Przyjęcie komunikatu nie należy do transakcji w BMTD, a dla CMTD jest jej częścią w przypadku atrybutu REQUIRED.

Za pomocą elementu activationConfig adnotacji @MessageDriven lub elementowi activation-config deskryptora wdrożenia dostawca ziarna (ang. bean provider) określa wdrażającemu (ang. deployer) konfigurację ziarna w środowisku uruchomieniowym, np. trybie potwierdzenia komunikatów, selektorów komunikatów, rodzajowi adresu przeznaczenia czy punktowi końcowemu. Konfiguracja w deskryptorze wdrożenia jest dodawana do tej określonej przez adnotację @MessageDriven. W przypadku wystąpienia parametru w obu konfiguracjach konfiguracja z deskryptora nadpisuje konfigurację z adnotacji.

Konfiguracja (aktywacji) ziarna MDB jest specyficzna dla dostawcy komunikacyjnego.

Właściwości konfiguracji aktywacji dla ziarna MDB opartego o dostawcę komunikacyjnego zgodnego z JMS:
  • Potwierdzenie otrzymania komunikatu realizowane jest wyłącznie przez kontener EJB (serwer aplikacyjny). W przypadku trybu CMTD komunikat potwierdzany jest jako część protokołu zatwierdzania transakcji. W BMTD przyjęcie komunikatu nie jest częścią transakcji inicjowanej przez ziarno MDB i jest potwierdzane przez kontener. Dla BMTD dostawca ziarna określa Auto-acknowledge lub Dups-ok-acknowledge jako wartość atrybutu acknowledgeMode elementu activationConfig adnotacji @MessageDriven lub activation-config-property w deskryptorze. Brak acknowledgeMode oznacza Auto-acknowledge.
  • Ziarno MDB może być związane z selektorem komunikatów, który określa (zawęża), jakie komunikaty mogą być dostarczone. Wartość atrybutu propertyName adnotacji @ActivationConfigProperty dla selektora to messageSelector, gdzie w atrybucie propertyValue określa się warunek, który musi być spełniony przez komunikaty dostarczane do ziarna, np. "JMSType = 'car' AND color = 'blue' and weight > 2500". Składający aplikację (ang. application assembler) może dalej zawęzić komunikaty przez zmianę wartości elementu activation-config-property-value deskryptora wdrożenia, ale nie całkowicie zamienić (w ramach obsługi komunikatu ziarno oczekuje pewnych wartości przenoszonych przez nie i zamiana selektora mogłaby doprowadzić do niepoprawnego działania ziarna).
  • Obowiązkiem wdrażającego ziarno jest określenie adresu przeznaczenia lub punktu końcowego, na którym nasłuchuje ziarno. Dostawca ziarna może jedynie wskazać (ale nie nakazać), czy ziarno ma nasłuchiwać na kolejce (ang. queue) lub temacie (ang. topic) przez element activationConfig adnotacji @MessageDriven lub activation-config-property w deskryptorze. Nazwa parametru to destinationType, dla którego wartością może być javax.jms.Queue lub javax.jms.Topic.
Jeśli ziarno związane jest z tematem, dostawca może dalej określić tryb subskrypcji ziarna - trwała (ang. durable) lub ulotna (ang. non-durable) w elemencie activationConfig adnotacji @MessageDriven lub activation-config-property w deskryptorze. Nazwa parametru to subscriptionDurability, dla którego wartością może być Durable lub NonDurable. Brak subscriptionDurability wskazuje na subskrypcję ulotną (nietrwałą). Trwała subskrypcja to taka, która gwarantuje dostarczenie komunikatu, nawet przy braku pracy kontenera EJB. Osoba wdrażająca ziarno MDB powinna zagwarantować, że z pojedyńczą kolejką nie związano wielu różnych ziaren MDB.

Zabronione jest, aby metoda komunikacyjna zgłosiła wyjątek java.rmi.RemoteException. Zaleca się niezgłaszanie wyjątków niedeklarowanych (ang. runtime exception).

Jeśli ziarno MDB pracujące w trybie BMTD zgłosi RuntimeException, kontener nie powinien potwierdzić otrzymania komunikatu.

Metody @PreDestroy mogą nie zostać wywołane na egzemplarzu ziarna MDB przy padzie serwera lub po zgłoszeniu wyjątku systemowego przez ziarno. Nie można polegać na wykonaniu @PreDstroy jako pary do wykonania @PostConstruct i utworzone zasoby muszą być w inny sposób sprzątane.

Dostawca ziarna MDB jest zobowiązany do bezpośredniego (przez implements) lub pośredniego (przez deklarację w @MessageDriven bądź deskryptorze) implementowania interfejsu komunikacyjnego wymaganego przez wspierany system komunikacyjny. Dla JMS będzie to javax.jms.MessageListener.

Klasa ziarna MDB musi być publiczna, nieoznaczona słowem final czy abstract i musi być klasą głównego poziomu (ang. top-level class). Klasa musi dostarczać publiczny bezparametrowy konstruktor. Klasa nie może definiować metody finalize(). Klasa może implementować interfejs javax.ejb.MessageDrivenBean, javax.ejb.TimedObject oraz posiadać metodę ejbCreate(). Klasa ziarna może posiadać klasy i interfejsy nadrzędne.

Kontener EJB musi wspierać ziarna MDB jako konsumentów kolejek JMS lub trwałej subskrypcji.

Pytanie konkursowe: Co definiuje element activationConfig adnotacji @MessageDriven? Pytanie "wyciągajace": Jaki element deskryptora wdrożenia ejb-jar.xml odpowiada elementowi activationConfig adnotacji @MessageDriven?

27 czerwca 2008

Obsługa formularza w JBoss Seam

2 komentarzy
Już miałem do czynienia z lekturą rozdziału 4. The contextual component model z dokumentacji JBoss Seam 2.0.3.CR1, ale jedynie w niewielkiej jego części. Dzisiaj za zadanie wybrałem obsługę formularza z wyświetleniem danych w strukturze drzewiastej i baczniejsze przyjrzenie się treści rozdziału wydało mi się jak najbardziej na miejscu. Do tej struktury drzewiastej przymierzam się od dobrych kilku dni, więc skoro już temat czeka kilka dni, to i tak jest zagrożony niedotrzymaniem terminu, a nowy - obsługa formularza - jest nowy, więc zagrożony dopiero może być i szkoda byłoby skończyć z dwoma zagrożonymi tematami (coś mi to przypomina komedię Barei, której tytułu nie mogę sobie przypomnieć, gdzie w jednej ze scen facet przychodzi odebrać samochód z naprawy, a on wciąż w naprawie, a nowe obsługiwane są od ręki).

W sekcji 4.2.4. JavaBeans można przeczytać:

By default, JavaBeans are bound to the event context.

I faktycznie, podczas uruchomienia mojej skromnej aplikacji rejestracja komponentu seamowego pozdrow związana jest z kontekstem EVENT (z książki Beginning JBoss Seam: From Novice to Professional dowiedziałem się, że kontekst EVENT to tak na prawdę stary dobry zasięg zlecenia - ang. request scope).
 [Component] Component: pozdrow, scope: EVENT, type: JAVA_BEAN, class: pl.jaceklaskowski.seam.PozdrowAction
Seam definiuje dwa podstawowe pojęcia: kontekst oraz komponent. Kontekst definiuje przestrzeń aktywności komponentów o ustalonej nazwie określanej adnotacją @Name lub stosowną konfiguracją w components.xml. Za pomocą mechanizmu bijekcji Seam potrafi przypisać zmiennym egzemplarza komponenty (adnotacja @In), których zmiana może być propagowana spowrotem do środowiska (adnotacja @Out). Żąglując nimi możemy automatycznie uzyskiwać dostęp do komponentów zewnętrznych i modyfikować je wraz z zapisem zmian "na zewnątrz". Można to przyrównać do dwukierunkowego IoC, gdzie przy @In odbiorcą referencji do obiektu jest nasza klasa, podczas gdy przy @Out będzie to przestrzeń całej aplikacji.

I jeszcze jedna ciekawa uwaga w kontekście użycia komponentów seamowych, które są POJO:

Seam JavaBean components may be instantiated using Component.getInstance() or @In(create=true). They should not be directly instantiated using the new operator.

Zatem, pozwalamy je tworzyć wyłącznie Seamowi, np. za pomocą @In(create=true).

I kolejna ciekawostka związana z POJO w roli komponentów seamowych:

In order to perform its magic (bijection, context demarcation, validation, etc), Seam must intercept component invocations. For JavaBeans, Seam is in full control of instantiation of the component, and no special configuration is needed.

Zaleca się, aby nazwa komponentów seamowych odpowiadała pakietowi, w jakiej klasa reprezentująca komponent się znajduje, aby ustrzec się przed konfliktem nazw. Istnieje możliwość skorzystania z aliasowania nazw, aby z pełnych stworzyć krótsze, jednowyrazowe. Będzie o tym jeszcze w kolejnych wpisach o Seamie.

Jeszcze kilka słów o konstrukcjach komponentów seamowych i lektura rozdziału 4-tego za mną.

Z tą wiedzą podchodzę do tematu stworzenia obsługi formularza w Seamie. Tworzę zwykłą klasę javową odpowiadającą polom na formularzu - pl.jaceklaskowski.seam.Kategoria.
 package pl.jaceklaskowski.seam;

import org.hibernate.validator.Length;
import org.hibernate.validator.NotNull;
import org.jboss.seam.annotations.Name;

@Name("kategoria")
public class Kategoria {
private String nazwa;
private String opis;
private Kategoria rodzic;

@NotNull @Length(min=5, max=15)
public String getNazwa() {
return nazwa;
}

public void setNazwa(String nazwa) {
this.nazwa = nazwa;
}

@NotNull @Length(min=5, max=15)
public String getOpis() {
return opis;
}

public void setOpis(String opis) {
this.opis = opis;
}

public Kategoria getRodzic() {
return rodzic;
}

public void setRodzic(Kategoria rodzic) {
this.rodzic = rodzic;
}

}
Warto zwrócić uwagę na adnotacje pochodzące z pakietu org.hibernate.validator - @NotNull oraz @Length, które gwarantują odpowiednią "jakość" danych w ramach komponentu kategoria. Skorzystanie z adnotacji @NotNull oraz @Length wymaga dodania zależności hibernate-commons-annotations.jar do projektu (podglądam root-2.0.3.CR1.pom, ale tam nie ma tej zależności wymienionej!):
 <dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>3.3.0.ga</version>
</dependency>
W przeciwnym przypadku - brak biblioteki hibernate-commons-annotations.jar - wykonanie aplikacji skutkuje zgłoszeniem wyjątku:
 java.lang.NoClassDefFoundError: org/hibernate/annotations/common/reflection/XMember
at org.jboss.seam.core.Validators.createValidator(Validators.java:122)
at org.jboss.seam.core.Validators.getValidator(Validators.java:105)
at org.jboss.seam.core.Validators.getValidator(Validators.java:88)
at org.jboss.seam.core.Validators$ValidatingResolver.setValue(Validators.java:199)
at org.jboss.el.parser.AstPropertySuffix.setValue(AstPropertySuffix.java:73)
at org.jboss.el.parser.AstValue.setValue(AstValue.java:84)
at org.jboss.el.ValueExpressionImpl.setValue(ValueExpressionImpl.java:249)
at com.sun.facelets.el.TagValueExpression.setValue(TagValueExpression.java:93)
at org.jboss.seam.core.Validators.validate(Validators.java:140)
at org.jboss.seam.ui.validator.ModelValidator.validate(ModelValidator.java:35)
at javax.faces.component._ComponentUtils.callValidators(_ComponentUtils.java:156)
at javax.faces.component.UIInput.validateValue(UIInput.java:288)
at javax.faces.component.UIInput.validate(UIInput.java:332)
at javax.faces.component.UIInput.processValidators(UIInput.java:144)
at javax.faces.component.UIComponentBase.processValidators(UIComponentBase.java:658)
at javax.faces.component.UIComponentBase.processValidators(UIComponentBase.java:658)
at javax.faces.component.UIForm.processValidators(UIForm.java:74)
at javax.faces.component.UIComponentBase.processValidators(UIComponentBase.java:658)
at javax.faces.component.UIViewRoot.access$101(UIViewRoot.java:43)
at javax.faces.component.UIViewRoot$2.process(UIViewRoot.java:97)
at javax.faces.component.UIViewRoot.process(UIViewRoot.java:205)
at javax.faces.component.UIViewRoot.processValidators(UIViewRoot.java:93)
at org.apache.myfaces.lifecycle.ProcessValidationsExecutor.execute(ProcessValidationsExecutor.java:32)
at org.apache.myfaces.lifecycle.LifecycleImpl.executePhase(LifecycleImpl.java:103)
at org.apache.myfaces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:76)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:148)
Tworzę stronę kategoria.xhtml, która zawiera formularz do wprowadzania nowych kategorii.
 <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<head>
<title>Administracja kategoriami</title>
</head>
<body>
<f:view>
<h:form>
<s:validateAll>
<h:panelGrid columns="2">
Nazwa: <h:inputText value="#{kategoria.nazwa}" required="true" />
Opis: <h:inputText value="#{kategoria.opis}" required="true" />
</h:panelGrid>
</s:validateAll>
<h:messages />
<h:commandButton value="Dodaj" action="#{kategoriaAgent.dodaj}" />
</h:form>
</f:view>
</body>
</html>
Na uwagę zasługuje skorzystanie z JSF EL w atrybutach value oraz action, które korzystają z komponentów seamowych kategoria oraz kategoriaAgent, jakby były zdefiniowane w faces-config.xml zgodnie z zasadami JavaServer Faces. Właśnie tutaj tkwi siła Seama, który znosi obowiązek utrzymywania pliku faces-config.xml.

Wspomniany komponent seamowy kategoriaAgent reprezentowany jest przez klasę pl.jaceklaskowski.seam.KategoriaAgent:
 package pl.jaceklaskowski.seam;

import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.log.Log;

@Name("kategoriaAgent")
public class KategoriaAgent {

@In
private Kategoria kategoria;

@Logger
private Log logger;

public void dodaj() {
logger.info("Tu nastąpi zapis kategorii \"#{kategoria.nazwa}\" do bazy danych");
}
}
Pozostaje sprawdzić działanie aplikacji uruchamiając stronę http://localhost:8080/seam-richfaces-tree/seam/kategoria.xhtml. Formularz wyświetla się poprawnie, a podanie poprawnych danych do formularza spowoduje wyświetlenie komunikatu na konsoli Geronimo:
 11:59:36,984 INFO  [Version] Hibernate Validator 3.0.0.GA
11:59:37,134 INFO [KategoriaAgent] Tu nastąpi zapis kategorii "Kategoria #1" do bazy danych
Dla kompletu informacji prezentuję deskryptor wdrożenia aplikacji webowej - /WEB-INF/web.xml:
 <?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
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>contextFactory</param-name>
<param-value>com.tonbeller.wcf.controller.RequestContextFactoryImpl</param-value>
</context-param>
<context-param>
<param-name>com.tonbeller.wcf.controller.RequestContextFactory</param-name>
<param-value>com.tonbeller.wcf.controller.RequestContextFactoryImpl</param-value>
</context-param>
<filter>
<filter-name>JPivotController</filter-name>
<filter-class>com.tonbeller.wcf.controller.RequestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>JPivotController</filter-name>
<url-pattern>*.seam</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>JPivotController</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>
<listener>
<listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
</listener>
<listener>
<listener-class>com.tonbeller.tbutils.res.ResourcesFactoryContextListener</listener-class>
</listener>
<context-param>
<param-name>facelets.VIEW_MAPPINGS</param-name>
<param-value>*.xhtml</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>*.seam</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/seam/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>10</session-timeout>
</session-config>
<jsp-config>
<taglib>
<taglib-uri>http://www.tonbeller.com/wcf</taglib-uri>
<taglib-location>/WEB-INF/wcf/wcf-tags.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://www.tonbeller.com/jpivot</taglib-uri>
<taglib-location>/WEB-INF/jpivot/jpivot-tags.tld</taglib-location>
</taglib>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<scripting-invalid>true</scripting-invalid>
<is-xml>false</is-xml>
</jsp-property-group>
</jsp-config>
<resource-ref>
<res-ref-name>jdbc/MondrianFoodmart</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
</web-app>
Nie mogę poradzić sobie z konfiguracją Seam z facelets i uruchomieniem znaczników JSP projektu jpivot. Facelets nie rozpoznaje znaczników JSP jpivota i są one po prostu umieszczane w stronie wynikowej. Dlatego też zdecydowałem się na podwójne mapowanie /seam/* oraz *.seam, tak abym miał możliwość tworzenia stron korzystających ze znaczników jpivota bez użycia facelets. Ma ktoś receptę na to, aby przekonać facelets do rozpoznania znaczników jpivota? Czy jest konieczne stworzenie własnej biblioteki znaczników dla facelets odpowiadających znacznikom JSP z jpivota?

Pytanie konkursowe (z tych bardziej wymagających): Dlaczego uruchomienie strony /seam-richfaces-tree/kategoria.seam zakończy się błędem HTTP ERROR: 404 - /seam-richfaces-tree/kategoria.jsp? I kolejne z serii dla wymagających: Dlaczego uruchomienie strony /seam-richfaces-tree/seam/kategoria.xhtml zakończy się poprawnym uruchomieniem strony kategoria.xhtml z jednoczesnym wykonaniem kontrolek JSF?

26 czerwca 2008

Dzień Scruma i Eclipse Ganymede publicznie dostępny

5 komentarzy
Mile byłem dzisiaj zaskoczony, kiedy podjechał do mnie FedEx z podarunkiem od NetBeans za mój udział w konkursie NetBeans Blogging Contest, i nie zawartość przesyłki mnie zaskoczyła, a sposób pokwitowania odbioru. Kurier przekazał mi urządzenie przenośne na którym elektronicznym podpisem (nie certyfikatem! ;-)) pokwitowałem odbiór. Przypomniała mi się podobna sytuacja, gdzie podczas pobytu w Stanach potwierdzałem transakcję płatniczą kartą kredytową i przy kasach nie było długopisów i zabawy z papierkami, a po prostu za pomocą rysika pisało się na aktywnym ekranie (jak na palmtopie czy podobnie). Bajera. W końcu pewnie trafi to do nas, jak koszty wdrożenia takiej zabawki zmniejszą się znacznie.

Więcej nadeszła wiekopomna chwila i wydano Eclipse Ganymede (Eclipse 3.4). Właśnie zainstalowałem Eclipse JEE Ganymede i działam. Pierwsze wrażenia neutralne, gdyż już wcześniej pracowałem z wydaniami rozwojowymi Eclipse Ganymede, a tylu zmian pomiędzy nimi nie było, aby lista funkcjonalności mnie zaskoczyła. Jeśli do niedawna pracowałem w dużej mierze z NetBeans 6.1 to teraz Eclipse Ganymede zajmuje mnie na większą część czasu. Okaże się jakie to będzie miało skutki. Niedługo NetBeans 6.5, więc wiele dobrego jeszcze przede mną. I wszystko całkowicie bezpłatnie!


Pewien nieznajomy (nazwijmy go MM zamiast NN) napisał do mnie:

Swoją drogą to Naszą Szkapę czytałeś jeszcze w podstawówce:P Analogia do tej lektury jest jeszcze jedna - frameworki wykonują za nas wiele ciężkiej pracy:)

Czyż to nie trafna analogia? Nie ma inaczej jak nazwać Seama naszą szkapą, którą każdy ewaluował (pobieżnie?) w podstawówce. ;-)

Dzisiaj miałem dzień Scruma. W życiu nie czytałem o tym, tylko coś tam podsłyszałem, a tu proszę jednego dnia, aż dwie prezentacje i kilka kolejnych w kolejce (!) Najpierw coś wewnętrzną pocztą - takie wprowadzenie do Scruma, a później natrafiłem na Jak wygląda w praktyce Scrum/XP w naszej firmie - nie byłeś zobacz :). Jakkolwiek obie to jedynie wprowadzenie do tematu, to warto im było się przyjrzeć i poczuć klimaty scrumowe. Już nie mogę doczekać się, aby wziąść udział w projekcie ze Scrumem w roli głównej.

25 czerwca 2008

Element jsp-property-group w JSP 2.1

1 komentarzy
Podczas rozpoznawania facelets w kontekście braku interpretowania znaczników JSP z projektu jpivot natrafiłem na pewną ciekawostkę JSP 2.1 - element jsp-property-group w deskryptorze rozmieszczenia aplikacji webowej /WEB-INF/web.xml. Jestem pewien, że jest to jeden z tych niuansów specyfikacji JSP 2.1, który potrafi położyć nawet najbardziej rozeznanych w zawiłościach specyfikacji podczas egzaminu Sun Certified Web Component Developer (SCWCD). Już kiedyś miałem okazję spotkać ten element, ale przelotnie i nigdy nie przyszło mi go zastosować.

Podczas lektury artykułu Developing applications with Facelets, JSF, and JSP z sierpnia 2006r. (!) napotkałem go ponownie.
 <jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<scripting-invalid>true</scripting-invalid>
<is-xml>true</is-xml>
</jsp-property-group>
</jsp-config>
Za pomocą jsp-property-group można konfigurować grupy stron JSP i przypisywać im parametry konfiguracyjne. W powyższym przykładzie jsp-property-group zostało wykorzystane do wyłączenia możliwości zanurzania skryptów JSP wewnątrz stron JSP o rozszerzeniu jsp. Innymi konfiguracjami mogłoby być kodowanie stron, włączenie obsługi JSP EL (języka wyrażeń JSP - tych rozpoczynających się od $) oraz dołączanie (pod)stron przed i po wykonaniu strony. Rozszeżenie stron JSP nie musi być koniecznie jsp, ale same strony muszą być poprawnymi stronami JSP.

Zgodnie ze specyfikacją JavaServer Pages 2.1 (JSP) w JSP.3.3 JSP Property Groups (strona 78) konfiguracja stron JSP za pomocą jsp-config (rodzica jsp-property-group) dotyczy wskazanych stron spełniających wzorzec w url-pattern oraz wszystkich włączonych stron za pomocą dyrektywy include za wyjątkiem parametru konfiguracyjnego page-encoding, który dotyczy wyłącznie tych spełniających url-pattern.

Jeśli wzorzec określony w url-pattern jest identyczny ze wzorcem elementu servlet-mapping, wtedy jsp-property-group ma priorytet.

Podelementy jsp-property-group:
  • el-ignored (true/false) - wyłącza/włącza JSP EL, który przed JSP 2.0 nie był dostępny. Zmiana globalnej konfiguracji możliwa jest przez atrybut isELIgnored dyrektywy page w stronie JSP. Parametr ma wpływ na obsługę \$ oraz \#, które wyłączają specjalność znaków $ oraz #. Przy wyłączonym JSP EL są one łańcuchem 2 znaków bez specjalności \, $ czy #;
  • scripting-invalid (true/false) - wyłącza/włącza mechanizm skryptów w stronach JSP. Domyślnie skrypty są włączone;
  • page-encoding - odpowiada atrybutowi pageEncoding dyrektywy page w stronie JSP i ustawia stronę kodową na podaną. Dotyczy wyłącznie stron JSP, a nie dokumentów JSP (jspx), które są dokumentami XML, gdzie podanie strony kodowej jest obowiązkowe. Przykład w specyfikacji przedstawia konfigurację dla podzbioru stron JSP spełniających wzorzec /ja/*, czyli tych należących do strony kodowej Shift_JIS;
  • include-prelude określa ścieżkę strony (fragmentu strony JSP - jspf) w aplikacji webowej dołączanego na początku strony JSP, podobnie do dyrektywy include. W przypadku wielu elementów include-prelude ich dołączanie będzie odpowiadało kolejności występowania w jsp-property-group;
  • include-coda jest odpowiednikiem include-prelude, ale dołączenie następuje na końcu stron JSP spełniających wzorzec url-pattern w sekcji jsp-property-group.
  • is-xml (true/false) wskazuje, że strony spełniające url-pattern są dokumentami XML. Za pomocą tego elementu można określić, że domyślne rozszerzenie wskazujące strony JSP jako dokumenty XML - jspx - jest rozszerzeniem zwykłych, niexmlowych stron JSP;
  • deferred-syntax-allowed-as-literal (true/false) - wyłącza/włącza specjalne traktowanie łańcuchu #{, które od JSP 2.1 są zarezerwowane dla wyrażeń JSP EL (wartość false). Za pomocą atrybutu deferredSyntaxAllowedAsLiteral dyrektywy page można zmienić wartość tego parametru dla pojedyńczej strony JSP;
  • trim-directivewhitespaces (true/false) usuwa/pozostawia "białe" znaki w odpowiedzi strony JSP. Wpływa istotnie na rozmiar wygenerowanej klientowi strony JSP. Nie ma wpływu na dokumenty JSP (strony JSP będące dokumentami XML). Domyślna wartość false - pozostawia "białe" znaki. Możliwa zmiana konfiguracji per strona JSP za pomocą atrybutu trimDirectiveWhitespaces dyrektywy page.
Dla kompletu informacji należałoby jeszcze wspomnieć o dyrektywie page - rozdział JSP.1.10.1 The page Directive specyfikacji JavaServer Pages 2.1 (strona 44). W stronach JSP dyrektywa page reprezentowana jest za pomocą elementu <%@ page { attr="value" }* %>, podczas gdy dla dokumentów JSP <jsp:directive.page>. Możliwe jest wielokrotne użycie dyrektywy page w ramach pojedyńczej strony i mogą być umiejscowione w dowolnym miejscu strony, a mimo to wpływają na całą jednostkę kompilacji (cała strona JSP już po dołączeniu podstron). Umiejscowienie dyrektywy page z atrybutami pageEncoding oraz contentType powinno być jednakże na początku strony, aby poprawnie wyznaczyć kodowanie strony. Jeśli dyrektywa page występuje w różnych częściach strony JSP to dla identycznych parametrów musi być identyczna wartość, za wyjątkiem atrybutów import oraz pageEncoding.

Składnia dyrektywy page:
 <%@ page page_directive_attr_list %>

page_directive_attr_list ::= { language="scriptingLanguage"}
{ extends="className" }
{ import="importList" }
{ session="true|false" }
{ buffer="none|sizekb" }
{ autoFlush="true|false" }
{ isThreadSafe="true|false" }
{ info="info_text" }
{ errorPage="error_url" }
{ isErrorPage="true|false" }
{ contentType="ctinfo" }
{ pageEncoding="peinfo" }
{ isELIgnored="true|false" }
{ deferredSyntaxAllowedAsLiteral="true|false"}
{ trimDirectiveWhitespaces="true|false"}
Opis poszczególnych elementów dyrektywy page znajduje się w tabeli Table JSP.1-8 Page Directive Attributes specyfikacji JavaServer Pages 2.1 (strona 46).

Pytanie konkursowe: Za pomocą jakiego elementu deskryptora aplikacji webowej /WEB-INF/web.xml konfigurowane są parametry stron JSP? Pytanie bardziej wyrafinowane, z tych wykańczających na egzaminach, czy "ostrych" rozmowach kwalifikacyjnych: Jakie elementy jsp-property-group w web.xml mogą być nadpisane przez dyrektywę page? Miłych rozmów kwalifikacyjnych ;-)

24 czerwca 2008

Nasza "szkapa" JBoss Seam a JPivot oraz polskie JUGi i Quo vadis studencie informatyki?

0 komentarzy
I kolejny problem z głowy! Paweł Wrzeszcz był na tyle łaskaw, że wskazał mi rozwiązanie mojego problemu braku rozpoznania klas oznaczonych adnotacją @Name jako komponentów seamowych (patrz: komentarz do Definicja komponentów seamowych - /WEB-INF/components.xml). Wystarczyło umieścić pusty (!) plik seam.properties w /WEB-INF/classes i już niepotrzebna była definicja komponentu pozdrow w /WEB-INF/components.xml. Jeszcze raz potwierdziło się moje przypuszczenie, że prowadzenie bloga ma swoje zalety, bo wiele dobrego można się z niego dowiedzieć. Ave Paweł!

Napisał do mnie pewien straceniec, który zamiast zaangażować się w ogrodnictwo czy prowadzenie własnej restauracji, albo zapisać się do harcerstwa i prowadzić jakąś grupę zuchów postanowił zniszczyć sobie kręgosłup i oczy, i w ogóle nabawić się wielu schorzeń próbując rozwiązywać problemy informatyczne ślęcząc przez komputerem godzinami (więcej o "ciekawostkach" długiego przesiadywania przy kompie w wątku Zdrowie programisty na grupie pl.comp.lang.java):

Kończę trzeci rok studiów, więc najwyższa pora dla mnie wybrać sobie dziedzinę IT w której chcę się specjalizować.
Skoro trzeba, to wybrałem. Dokładniej ( jak można się domyślić z kontekstu ) wybrałem mniej więcej to w czym już Pan orientuje się bardzo dobrze, najkrócej mówiąc programowanie w Javie po stronie serwera.
Z tego co znalazłem w internecie widzę, że dziedzina ta jest bardzo obszerna i szczerze mówiąc jej zakres jak na teraz trochę mnie przerasta.
I tu wkracza moja prośba: byłbym bardzo wdzięczny, gdyby mógł Pan napisać mi od czego zacząć przygody z tymi właśnie technologiami Javy.
Oczywiście nie jestem aż taki początkujący, programuję ( tak sobie i w czymkolwiek, ale jednak ) od dobrych sześciu lat, więc to i owo umiem.
Java sama w sobie również obca mi nie jest. Jednak sam nie jestem w stanie wskazać sobie odpowiedniej drogi.


Odpowiedziałem, bo jak tu nie pomóc bratniej duszy (mimo tego Pana!). Postanowiłem opublikować odpowiedź, gdyż nie jest to pierwszy raz, kiedy otrzymuję tego typu zapytanie i zawsze coś innego mnie w danej chwili interesuje, a to skutkuje innym propozycjami tematów do rozpoznania jako warte uwagi. Teraz na fali mam Seama, więc nie obyło się bez Seama, ale nie tylko. Oto moja odpowiedź:

Sądzę, że gdybyś zabrał się za poznawanie JBoss Seama, którego właśnie wałkuję mógłbyś wiele zyskać. Do tego dodać Groovy, GWT, Wicket czy Spring Framework i robi się niezwykle ciekawie. Gdybyś jednak potrzebował coś z wyższej półki to polecam OSGi właśnie po stronie serwera w wykonaniu chociażby S2AS - SpringSource Application Server lub jak kto woli - Spring Dynamic Modules. Dużo dobrego można spodziewać się w nadchodzącym czasie od tej technologii. OSGi jest trochę naciągane na stronę serwera, ale już SCA w wykonaniu Apache Tuscany to pierwsza liga. Zatem wybieraj coś łatwego i przyjemnego, jak Seam z Apache Geronimo (poznajesz EJB3 i JSF z innymi zabawkami), albo pierwsza liga w wykonaniu OSGi i/lub SCA.

I jak? Nie za ciężka dla początkującego programisty? Coś dodać/ująć? Za dużo hype'u?

Poza wspomnianym zapytaniem znalazłem jeszcze jedną podobną wiadomość w mojej skrzynce pocztowej z pytaniem dotyczącym liczby grup javowych (JUG = Java User Group) w Polsce:

Mam takie pytanie - ile jest dokładnie JUG'ów? Tarnów jest jakoś 9. czy źle liczę?

Pewnie, że źle, bo JUGów po JAVArsovii 2008 jak mrówków ;-) Tyle jest JUGów w Polsce, że możnaby raz w miesiącu odwiedzić jeden i wypełnić sobie wyjazdami cały rok! Niesamowite, ile się dzieje wokół inicjatyw javowych. W końcu doczekałem momentu, kiedy można bz trudu dostrzec, że polska społeczność javowa rozruszała się i wiele ciekawych wydarzeń javowych przed nami. Już oczyma wyobraźni widzę kolejne konferencje organizowane przez Warszawa JUG wespół z innymi JUGami (z Krakowem również :P).

Odpowiedź na postawione pytanie o liczbę JUGów w Polsce jest warta opublikowania (kolejność przypadkowa):
Mam nadzieję, że żadnego nie ominąłem - nie wybaczyłbym sobie.

Dziękuję tym samym MaGowi (gość od domeny jug.pl) za udostępnienie nam miejsca w przestrzeni jug.pl. Chwała i sława jego! Ave Marek!

A teraz do Seama. Postanowiłem zmienić sposób rozmieszczania aplikacji, pozbywając się konieczności podawania planu podczas deploy. Przeniesienie pliku geronimo-web.xml do katalogu WEB-INF znosi konieczność podawania go explicite - Geronimo wczyta plik /WEB-INF/geronimo-web.xml automatycznie podczas rozmieszczania aplikacji. Oczywiście zmiana wymaga usunięciem parametru konfiguracyjnego modulePlan dla geronimo-maven-plugin przy zadaniu deploy-module.
 <plugins>
<plugin>
<groupId>org.apache.geronimo.buildsupport</groupId>
<artifactId>geronimo-maven-plugin</artifactId>
<version>2.1.1</version>
...
<executions>
...
<execution>
<id>deploy-module</id>
<phase>pre-integration-test</phase>
<goals>
<goal>deploy-module</goal>
</goals>
<configuration>
<moduleArchive>target/seam-richfaces-tree.war</moduleArchive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
Nadeszła w końcu pora sprawdzić działanie JPivota w środowisku seamowym. Głównym kryterium ostatniego skoncentrowania swoich wysiłków na JBoss Seam była właśnie możliwość uruchomienia JPivot, który jest zbiorem znaczników JSP do obsługi bazy OLAP (miałem użyć do zbudowania aplikacji szkieletu Apache Wicket, którym wciąż jestem zachwycony, ale właśnie z braku możliwości uruchomienia znaczników JSP byłem zmuszony do "czasowej migracji" w stronę innego rozwiązania i padło na Seama). Poza możliwością uruchomienia znaczników JSP w Seamie mam gotową integrację JSF i EJB3 wraz ze sprawdzoną integracją z gotowymi bibliotekami kontrolek jsfowych jak RichFaces, ICEFaces czy Tomahawk, więc z dostępnych szkieletów webowych umożliwiających skorzystanie z JSP wybrałem właśnie JBoss Seam. W zasadzie to nie przychodzi mi do głowy nic bardziej trafnego z dostępnych szkieletów aplikacyjnych.

Jest kilka zmian, jakie muszę wykonać, aby uruchomić JPivota w stronach XHTML. Korzystam z reprezentacji xmlowej strony JSP, która opisana jest w rozdziale 10 XML View specyfikacji JavaServer Pages 2.1. Przy takim podejściu znacznik
 <%@ taglib uri="http://www.tonbeller.com/jpivot" prefix="jp" %>
staje się atrybutem znacznika html
 <html xmlns="http://www.w3.org/1999/xhtml" 
xmlns:f="http://java.sun.com/jsf/core"
xmlns:jp="http://www.tonbeller.com/jpivot"
xmlns:wcf="http://www.tonbeller.com/wcf">
...
</html>
JPivot nie jest opublikowany w repozytoriach mavenowych, więc mam dwa podejścia do rozwiązania tematu:
  1. umieścić biblioteki w odpowiednim katalogu projektu, np. src\main\webapp\WEB-INF\lib
  2. zainstalować biblioteki w lokalnym repozytorium mavenowym
Na chwilę obecną wybieram podejście numer 1.

Okazuje się, że podczas rozmieszczenia aplikacji z bibliotekami jpivot oraz pomocniczego wcf pojawiają się błędy związane z poprawnością deskryptorów znaczników JSP - plikach tld (ang. tag library descriptor).

Koniecznie musiałem wprowadzić zmiany w wcf-tags.tld (w WEB-INF/lib/wcf.jar), gdyż:
 org.apache.xmlbeans.XmlException: Invalid deployment descriptor: errors:

c:\geronimo\jar:file:C:\geronimo\repository\pl\jaceklaskowski\seam\seam-richfaces-tree\1.0\seam-richfaces-tree-1.0.war\
WEB-INF\lib\wcf.jar!\META-INF\wcf-tags.tld:6:1: error: cvc-datatype-valid.1.1: string value 'Web Component Framework Tags'
does not match pattern for tld-canonical-nameType in namespace http://java.sun.com/xml/ns/javaee

c:\geronimo\jar:file:C:\geronimo\repository\pl\jaceklaskowski\seam\seam-richfaces-tree\1.0\seam-richfaces-tree-1.0.war\
WEB-INF\lib\wcf.jar!\META-INF\wcf-tags.tld:25:1: error: cvc-enumeration-valid: string value 'EMPTY' is not a valid enumeration
value for body-contentType in namespace http://java.sun.com/xml/ns/javaee
Wystarczyło zmienić short-name na nazwę bez spacji
 <short-name>WebComponentFrameworkTags</short-name>
oraz
 <body-content>EMPTY</body-content>
na
 <body-content>empty</body-content>
Podobnie z /WEB-INF/jpivot/jpivot-tags.tld:
 23:18:13,859 WARN  [JspModuleBuilderExtension] Invalid transformed taglib
org.apache.xmlbeans.XmlException: Invalid deployment descriptor: errors:

C:\geronimo\repository\pl\jaceklaskowski\seam\seam-richfaces-tree\1.0\seam-richfaces-tree-1.0.war\
WEB-INF\jpivot\jpivot-tags.tld:87:1: error: cvc-enumeration-valid: string value 'EMPTY' is not a valid enumeration value
for body-contentType in namespace http://java.sun.com/xml/ns/javaee
Po tym temat miałem z głowy. Ale to nie wszystko. Nie, nie.

Podczas uruchamiania aplikacji pojawił się CNFE:
 Caused by: java.lang.ClassNotFoundException: com.tonbeller.wcf.format.BooleanHandler in classloader 
org.apache.geronimo.configs/myfaces/2.1.1/car
at org.apache.commons.digester.Digester.createSAXException(Digester.java:3181)
at org.apache.commons.digester.Digester.createSAXException(Digester.java:3207)
at org.apache.commons.digester.Digester.startElement(Digester.java:1456)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:533)
at com.sun.org.apache.xerces.internal.parsers.AbstractXMLDocumentParser.emptyElement(AbstractXMLDocumentParser.java:220)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanStartElement(XMLDocumentFragmentScannerImpl.java:872)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(XMLDocumentFragmentScannerImpl.java:1693)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:368)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:834)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:764)
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:148)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1242)
at org.apache.commons.digester.Digester.parse(Digester.java:1745)
at com.tonbeller.wcf.format.FormatterFactory.fillFormatter(FormatterFactory.java:63)
... 33 more
Dodałem do geronimo-web.xml element inverse-classloading, co ostatecznie dało następujący plik-plan geronimo-web.xml:
 <?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://geronimo.apache.org/xml/ns/j2ee/web-2.0.1">
<environment xmlns="http://geronimo.apache.org/xml/ns/deployment-1.2">
<moduleId>
<groupId>pl.jaceklaskowski.seam</groupId>
<artifactId>seam-richfaces-tree</artifactId>
<version>1.0</version>
<type>war</type>
</moduleId>
<dependencies>
<dependency>
<groupId>org.apache.geronimo.configs</groupId>
<artifactId>system-database</artifactId>
</dependency>
</dependencies>
<inverse-classloading />
</environment>
<context-root>/seam-richfaces-tree</context-root>
<resource-ref>
<ref-name>jdbc/MondrianFoodmart</ref-name>
<resource-link>NoTxDatasource</resource-link>
</resource-ref>
</web-app>
Znaczenie <inverse-classloading /> to zmiana kolejności odszukiwania klas w zarządcach klas (ang. classloader) w Geronimo, tak że klasy poszukiwane są najpierw w zasobach aplikacji, a następnie wyżej w hierarchii zarządców, w samym Geronimo. Kolejny problem odszedł w zapomnienie.

Musiałem usunąć również jsf-api.jar, jstl.jar, junit.jar z WEB-INF/lib, które dokumentacja JPivot nakazuje, aby przekopiować z przykładów, gdzie one są zawarte. W przeciwnym przypadku można natknąć się na następujący LE:
 java.lang.LinkageError: loader constraints violated when linking javax/faces/validator/Validator class
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2395)
at java.lang.Class.getDeclaredMethods(Class.java:1763)
at org.apache.xbean.finder.ClassFinder.<init>(ClassFinder.java:162)
at org.apache.geronimo.jasper.deployment.JspModuleBuilderExtension.createJspClassFinder(JspModuleBuilderExtension.java:181)
at org.apache.geronimo.jasper.deployment.JspModuleBuilderExtension.addGBeans(JspModuleBuilderExtension.java:149)
at org.apache.geronimo.jetty6.deployment.JettyModuleBuilder.addGBeans(JettyModuleBuilder.java:518)
at org.apache.geronimo.j2ee.deployment.SwitchingModuleBuilder.addGBeans(SwitchingModuleBuilder.java:165)
at org.apache.geronimo.j2ee.deployment.EARConfigBuilder.buildConfiguration(EARConfigBuilder.java:647)
at org.apache.geronimo.deployment.Deployer.deploy(Deployer.java:254)
at org.apache.geronimo.deployment.Deployer.deploy(Deployer.java:133)
...
Po zmianie inverse-classloading mam:
 [JNDIResourceProvider] key not found: tbeller.bundles
[JNDIResourceProvider] key not found: tbeller.home
[JNDIResourceProvider] key not found: tbeller.locale
[JNDIResourceProvider] key not found: tbeller.properties
[Contexts] starting up: org.jboss.seam.web.session
[Contexts] starting up: org.jboss.seam.security.identity
[RequestFilter] >>> Request http://localhost:8080/seam-richfaces-tree/jpivot.seam[null][?null]
[RequestSynchronizer] Log normal request Thread = DefaultThreadPool 154, resultURI = null, currentThread = null
[RequestSynchronizer] Log handle-normal Thread = DefaultThreadPool 154,
resultURI = /seam-richfaces-tree/jpivot.seam, currentThread = Thread[DefaultThreadPool 154,5,m
[RequestFilter] redirecting to /seam-richfaces-tree/index.jsp
[RequestFilter] Request Execution total time: 0 ms
przy wywołaniu strony jpivot.xhtml, która prezentuje się następująco (strona oparta jest o Facelets):
 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition
xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:rich="http://richfaces.org/rich"
xmlns:jp="http://www.tonbeller.com/jpivot"
xmlns:wcf="http://www.tonbeller.com/wcf"
template="szablon.xhtml">

<ui:define name="body">
<jp:mondrianQuery
id="query01" dataSource="jdbc/MondrianFoodmart" catalogUri="/WEB-INF/queries/FoodMart.xml">
select
{[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales]} on columns,
{([Promotion Media].[All Media], [Product].[All Products])} ON rows
from Sales
where ([Time].[1997])
</jp:mondrianQuery>
</ui:define>

</ui:composition>
Mimo zmian nie udało mi się poprawnie uruchomić JPivot z Seamem na Geronimo, gdyż wciąż zamiast wykonania znacznika jp:mondrianQuery dostawałem stronę, jakby znacznik nie został rozpoznany i wypisywany był na stronie dosłownie. Coś było nie tak, ale po kilku kwadransach miałem dosyć i postanowiłem sprawdzić uruchomienie strony pochodzącej z przykładów jpivota - mondrian.jsp (znajduje się w archiwum jpivot.war w katalogu /WEB-INF/queries).

Tym razem uruchomienie http://localhost:8080/seam-richfaces-tree/mondrian.jsp było znacznie ciekawsze, bo wskazywało na jakąś pracę jpivota:
 00:57:36,281 INFO  [JNDIResourceProvider] key not found: tbeller.bundles
00:57:36,281 INFO [JNDIResourceProvider] key not found: tbeller.home
00:57:36,281 INFO [JNDIResourceProvider] key not found: tbeller.locale
00:57:36,281 INFO [JNDIResourceProvider] key not found: tbeller.properties
00:58:15,812 INFO [Contexts] starting up: org.jboss.seam.web.session
00:58:15,812 INFO [Contexts] starting up: org.jboss.seam.security.identity
00:58:16,265 INFO [RequestFilter] >>> Request http://localhost:8080/seam-richfaces-tree/mondrian.jsp[null][?null]
00:58:16,265 INFO [RequestSynchronizer] Log normal request Thread = DefaultThreadPool 151,
resultURI = null, currentThread = null
00:58:16,265 INFO [RequestSynchronizer] Log handle-normal Thread = DefaultThreadPool 151,
resultURI = /seam-richfaces-tree/mondrian.jsp, currentThread = Thread[DefaultThreadPool 151,5,main]
00:58:16,265 INFO [RequestFilter] redirecting to /seam-richfaces-tree/jpivot.seam
00:58:16,265 INFO [RequestFilter] Request Execution total time: 0 ms
00:58:16,265 INFO [RequestFilter] >>> Request http://localhost:8080/seam-richfaces-tree/jpivot.seam[null][?null]
00:58:16,265 INFO [RequestSynchronizer] Log normal request Thread = DefaultThreadPool 152,
resultURI = /seam-richfaces-tree/mondrian.jsp, currentThread = null
00:58:16,265 INFO [RequestSynchronizer] Log handle-normal Thread = DefaultThreadPool 152,
resultURI = /seam-richfaces-tree/jpivot.seam, currentThread = Thread[DefaultThreadPool 152,5,main]
...
00:58:18,296 INFO [RequestFilter] Request Execution total time: 2031 ms
00:58:59,359 INFO [RequestFilter] >>> Request http://localhost:8080/seam-richfaces-tree/mondrian.jsp[null][?null]
00:58:59,359 INFO [RequestSynchronizer] Log normal request Thread = DefaultThreadPool 153,
resultURI = /seam-richfaces-tree/jpivot.seam, currentThread = null
00:58:59,359 INFO [RequestSynchronizer] Log handle-normal Thread = DefaultThreadPool 153,
resultURI = /seam-richfaces-tree/mondrian.jsp, currentThread = Thread[DefaultThreadPool 153,5,main]
00:59:04,843 INFO [OlapModelTag] enter
00:59:04,859 INFO [MondrianModelFactory] using data source jdbc/MondrianFoodmart
00:59:04,859 INFO [MondrianModelFactory] Config[jdbcUrl=null, jdbcDriver=null, jdbcUser=null, jdbcPassword=null,
dataSource=jdbc/MondrianFoodmart,
schemaUrl="file:/C:/geronimo/repository/pl/jaceklaskowski/seam/seam-richfaces-tree/1.0/seam-richfaces-tree-1.0.war/WEB-INF/queries/FoodMart.xml",
mdxQuery=
select
{[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales]} on columns,
{([Promotion Media].[All Media], [Product].[All Products])} ON rows
from Sales
where ([Time].[1997])
, role=null, dynResolver=null, connectionPooling=null, externalDataSource=null, dynLocale=null, dataSourceChangeListener=null]
00:59:04,859 INFO [MondrianModelFactory]
ConnectString=provider=Mondrian;DataSource=java:comp/env/jdbc/MondrianFoodmart;
Catalog="file:/C:/geronimo/repository/pl/jaceklaskowski/seam/seam-richfaces-tree/1.0/seam-richfaces-tree-1.0.war/WEB-INF/queries/FoodMart.xml"
00:59:05,140 INFO [MondrianModel] setMdxQuery:
select
{[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales]} on columns,
{([Promotion Media].[All Media], [Product].[All Products])} ON rows
from Sales
where ([Time].[1997])
00:59:05,140 INFO [MondrianModel] connectString=provider=Mondrian;DataSource=java:comp/env/jdbc/MondrianFoodmart;
Catalog="file:/C:/geronimo/repository/pl/jaceklaskowski/seam/seam-richfaces-tree/1.0/seam-richfaces-tree-1.0.war/WEB-INF/queries/FoodMart.xml"
00:59:05,140 INFO [MondrianModel] jdbcDriver=null
00:59:05,156 INFO [Log4jStateLogger] initialize default
00:59:05,156 INFO [OlapModelProxy] initializing: com.tonbeller.jpivot.olap.model.CachingOlapModel@955df
00:59:05,156 INFO [MondrianModel] com.tonbeller.jpivot.mondrian.MondrianModel@12ff4b6
00:59:05,203 WARN [MondrianProperties] mondrian.properties can't be found under 'c:\geronimo\.' or classloader
00:59:05,203 INFO [MondrianProperties] Mondrian: loaded 0 system properties
00:59:05,218 INFO [MondrianModel] connectProperties=provider=Mondrian; DataSource=java:comp/env/jdbc/MondrianFoodmart;
Catalog=file:/C:/geronimo/repository/pl/jaceklaskowski/seam/seam-richfaces-tree/1.0/seam-richfaces-tree-1.0.war/WEB-INF/queries/FoodMart.xml
00:59:05,484 INFO [DefaultFileReplicator] Using "C:\geronimo\var\temp\vfs_cache" as temporary files store.
00:59:06,343 ERROR [OlapModelTag]
mondrian.olap.MondrianException: Mondrian Error:Internal error: while building member cache;
sql=[select "time_by_day"."the_year" from "time_by_day" as "time_by_day" group by "time_by_day"."the_year" order by "time_by_day"."the_year" ASC]
at mondrian.resource.MondrianResource$_Def0.ex(MondrianResource.java:785)
at mondrian.olap.Util.newInternal(Util.java:1340)
at mondrian.olap.Util.newError(Util.java:1356)
at mondrian.rolap.SqlStatement.handle(SqlStatement.java:211)
at mondrian.rolap.SqlStatement.execute(SqlStatement.java:142)
at mondrian.rolap.RolapUtil.executeQuery(RolapUtil.java:243)
at mondrian.rolap.RolapUtil.executeQuery(RolapUtil.java:204)
at mondrian.rolap.SqlMemberSource.getMemberChildren2(SqlMemberSource.java:719)
at mondrian.rolap.SqlMemberSource.getMemberChildren(SqlMemberSource.java:649)
at mondrian.rolap.SqlMemberSource.getMemberChildren(SqlMemberSource.java:624)
at mondrian.rolap.SmartMemberReader.readMemberChildren(SmartMemberReader.java:237)
at mondrian.rolap.SmartMemberReader.getMemberChildren(SmartMemberReader.java:201)
at mondrian.rolap.SmartMemberReader.getMemberChildren(SmartMemberReader.java:169)
at mondrian.rolap.SmartMemberReader.getMemberChildren(SmartMemberReader.java:159)
at mondrian.rolap.RolapUtil.lookupMemberInternal(RolapUtil.java:136)
at mondrian.rolap.RolapUtil.lookupMember(RolapUtil.java:102)
at mondrian.rolap.SmartMemberReader.lookupMember(SmartMemberReader.java:209)
at mondrian.rolap.RolapHierarchy.init(RolapHierarchy.java:249)
at mondrian.rolap.RolapCubeHierarchy.init(RolapCubeHierarchy.java:323)
at mondrian.rolap.RolapDimension.init(RolapDimension.java:158)
at mondrian.rolap.RolapCube.init(RolapCube.java:1103)
at mondrian.rolap.RolapCube.<init>(RolapCube.java:286)
at mondrian.rolap.RolapSchema.load(RolapSchema.java:438)
at mondrian.rolap.RolapSchema.load(RolapSchema.java:335)
at mondrian.rolap.RolapSchema.<init>(RolapSchema.java:226)
at mondrian.rolap.RolapSchema.<init>(RolapSchema.java:79)
at mondrian.rolap.RolapSchema$Pool.get(RolapSchema.java:924)
at mondrian.rolap.RolapSchema$Pool.get(RolapSchema.java:733)
at mondrian.rolap.RolapConnection.<init>(RolapConnection.java:152)
at mondrian.rolap.RolapConnection.<init>(RolapConnection.java:83)
at mondrian.olap.DriverManager.getConnection(DriverManager.java:190)
at mondrian.olap.DriverManager.getConnection(DriverManager.java:154)
at com.tonbeller.jpivot.mondrian.MondrianModel.initialize(MondrianModel.java:518)
at com.tonbeller.jpivot.olap.model.OlapModelDecorator.initialize(OlapModelDecorator.java:132)
at com.tonbeller.jpivot.tags.OlapModelProxy$MyState.initialize(OlapModelProxy.java:77)
at com.tonbeller.jpivot.tags.StackStateManager.initializeAndShow(StackStateManager.java:76)
at com.tonbeller.jpivot.tags.OlapModelProxy.initializeAndShow(OlapModelProxy.java:160)
at com.tonbeller.jpivot.tags.OlapModelTag.doEndTag(OlapModelTag.java:81)
at org.apache.jsp.mondrian_jsp._jspx_meth_jp_005fmondrianQuery_005f0(mondrian_jsp.java:137)
at org.apache.jsp.mondrian_jsp._jspService(mondrian_jsp.java:81)
at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:388)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:320)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:266)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
at org.apache.geronimo.jetty6.InternalJettyServletHolder.handle(InternalJettyServletHolder.java:65)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1093)
at com.tonbeller.wcf.controller.RequestFilter$MyHandler.normalRequest(RequestFilter.java:139)
at com.tonbeller.wcf.controller.RequestSynchronizer.handleRequest(RequestSynchronizer.java:127)
at com.tonbeller.wcf.controller.RequestFilter.doFilter(RequestFilter.java:263)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:360)
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:726)
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
at org.apache.geronimo.jetty6.handler.TwistyWebAppContext.access$101(TwistyWebAppContext.java:40)
at org.apache.geronimo.jetty6.handler.TwistyWebAppContext$TwistyHandler.handle(TwistyWebAppContext.java:65)
at org.apache.geronimo.jetty6.handler.ThreadClassloaderHandler.handle(ThreadClassloaderHandler.java:46)
at org.apache.geronimo.jetty6.handler.InstanceContextHandler.handle(InstanceContextHandler.java:58)
at org.apache.geronimo.jetty6.handler.UserTransactionHandler.handle(UserTransactionHandler.java:48)
at org.apache.geronimo.jetty6.handler.ComponentContextHandler.handle(ComponentContextHandler.java:47)
at org.apache.geronimo.jetty6.handler.TwistyWebAppContext.handle(TwistyWebAppContext.java:59)
at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:206)
at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:139)
at org.mortbay.jetty.Server.handle(Server.java:324)
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:505)
at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:828)
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:514)
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:211)
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:380)
at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:395)
at org.apache.geronimo.pool.ThreadPool$1.run(ThreadPool.java:214)
at org.apache.geronimo.pool.ThreadPool$ContextClassLoaderRunnable.run(ThreadPool.java:344)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:650)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:675)
at java.lang.Thread.run(Thread.java:595)
Caused by: java.sql.SQLException: Table/View 'time_by_day' does not exist.
at org.apache.derby.impl.jdbc.SQLExceptionFactory.getSQLException(Unknown Source)
at org.apache.derby.impl.jdbc.Util.generateCsSQLException(Unknown Source)
at org.apache.derby.impl.jdbc.TransactionResourceImpl.wrapInSQLException(Unknown Source)
at org.apache.derby.impl.jdbc.TransactionResourceImpl.handleException(Unknown Source)
at org.apache.derby.impl.jdbc.EmbedConnection.handleException(Unknown Source)
at org.apache.derby.impl.jdbc.ConnectionChild.handleException(Unknown Source)
at org.apache.derby.impl.jdbc.EmbedStatement.execute(Unknown Source)
at org.apache.derby.impl.jdbc.EmbedStatement.executeQuery(Unknown Source)
at org.apache.derby.iapi.jdbc.BrokeredStatement.executeQuery(Unknown Source)
at org.tranql.connector.jdbc.StatementHandle.executeQuery(StatementHandle.java:49)
at mondrian.rolap.SqlStatement.execute(SqlStatement.java:128)
... 75 more
...
00:59:06,359 INFO [RequestFilter] redirecting to error page /error.jsp
00:59:06,359 INFO [RequestFilter] Request Execution total time: 7000 ms
11:48:48,937 INFO [OlapModelProxy] session timeout
W końcu się coś ruszyło! Jutro zabieram się za dalsze usprawnianie aplikacji seamowej. Na zakończenie jeszcze prezentacja konfiguracji jpivota w środowisku webowej aplikacji seamowej:
 <?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.tonbeller.wcf.controller.RequestContextFactory</param-name>
<param-value>com.tonbeller.wcf.controller.RequestContextFactoryImpl</param-value>
</context-param>
<filter>
<filter-name>JPivotController</filter-name>
<filter-class>com.tonbeller.wcf.controller.RequestFilter</filter-class>
<init-param>
<description>forward to this page if session is new</description>
<param-name>indexJSP</param-name>
<param-value>/mondrian.jsp</param-value>
</init-param>
<init-param>
<description> This page is displayed if a the user clicks on a query before the previous query has finished
</description>
<param-name>busyJSP</param-name>
<param-value>/busy.jsp</param-value>
</init-param>
<init-param>
<description>URI of error page</description>
<param-name>errorJSP</param-name>
<param-value>/error.jsp</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>JPivotController</filter-name>
<url-pattern>*.seam</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>JPivotController</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>
<listener>
<listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
</listener>
<listener>
<listener-class>com.tonbeller.tbutils.res.ResourcesFactoryContextListener</listener-class>
</listener>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</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>*.seam</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>10</session-timeout>
</session-config>
<jsp-config>
<taglib>
<taglib-uri>http://www.tonbeller.com/wcf</taglib-uri>
<taglib-location>/WEB-INF/wcf/wcf-tags.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://www.tonbeller.com/jpivot</taglib-uri>
<taglib-location>/WEB-INF/jpivot/jpivot-tags.tld</taglib-location>
</taglib>
</jsp-config>
<resource-ref>
<res-ref-name>jdbc/MondrianFoodmart</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
</web-app>
Pewnie wciąż jeszcze zastanawiasz się, co wspólnego ten wpis ma ze szkolną lekturą "Nasza Szkapa" i skąd to ujęcie w cudzysłowy słowa "szkapa" w tytule wpisu?! Właśnie dzisiaj przyszedł mi do głowy akronim, który postanowiłem upublicznić - SzkApa. Zwrot znaczy ni mniej ni więcej tylko Szkielet Aplikacyjny (trzy pierwsze litery z pierwszego słowa i dwa z drugiego + 'a'). I tym samym na Seama padł cień "Naszej Szkapy" ;-) A może "Nasza Szkapa" to jedynie preludium do Seama i każdy zdroworozsądkowy programista korzystający z Seama wręcz nakazane ma przeczytanie szkolnej lektury?! Ja mam to już za sobą - czytałem przed maturą. A Ty?

Pytanie konkursowe: Jaki element konfiguracyjny JBoss Seam sprawia, że w samodzielnej, webowej aplikacji seamowej rozpoznane zostaną własne komponenty seamowe oznaczone @Name? (zakłada się brak pliku /WEB-INF/components.xml).

22 czerwca 2008

Definicja komponentów seamowych - /WEB-INF/components.xml

10 komentarzy
Wczorajsze problemy opisane w Kolejne dni z JBoss Seam - trochę o geronimo-maven-plugin i @Name wynikały z mojej niewiedzy jak JBoss Seam zarządza komponentami - bytami dostarczającymi funkcjonalności aplikacji. Szczęśliwie, rozwiązanie należy, do tych, które określam "zachowującym czystość technologiczną" i klasy seamowe tak na prawdę są zwykłymi klasami, a ich konfiguracja została umieszczona poza nimi - w pliku xmlowym. To zawsze będzie temat dyskusji, czy adnotacje (będące przypisami do kodu) powinny być jak najbliżej niego, tj. w nim samym, czy poza nim w pliku konfiguracyjnym, który zwykle jest plikiem xmlowym. Decyzję pozostawiam zespołom projektowym, a na chwilę obecną liczyła się dla mnie po prostu możliwość szybkiego utworzenia aplikacji, więc bez zbędnego rozważania, czy właściwe, czy nie, sięgnąłem po wyniesieniu konfiguracji aplikacji seamowej na zewnętrz, do pliku xml - /WEB-INF/components.xml.

Wczoraj skończyłem moje doświadczenia seamowe utworzeniem klasy pl.jaceklaskowski.seam.PozdrowAction z adnotacją @Name (Kolejne dni z JBoss Seam - trochę o geronimo-maven-plugin i @Name).
 package pl.jaceklaskowski.seam;

import java.io.Serializable;

import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.log.Log;

@Name("pozdrow")
public class PozdrowAction implements Serializable {

private static final long serialVersionUID = 1L;

@Logger Log log;

@Create
public void init() {
log.info("Inicjacja PodrowAction za nami");
}

public String getPozdrowienie() {
return "Pozdrowienia z wnętrza PozdrowAction";
}
}
W ten sposób (wydawało mi się, że) zdefiniowałem komponent seamowy pozdrow na wzór jsfowego ziarna zarządzanego. Nic bardziej mylnego. Kiedy uruchomiłem komponent z dedykowaną dla niego stroną (za moment o niej) nic szczególnego się nie wydarzyło - zero komunikatów w dzienniku zdarzeń (log) Geronimo, a wywołanie #{pozdrow.pozdrowienie} zwracało pusty wynik. Z założenia skorzystanie z adnotacji @Name powinno zwolnić mnie z konieczności utworzenia pliku konfiguracyjnego xml, ale niedługo trwało zanim okazało się, że niestety, ale plik jest konieczny.

Nic poza zdefiniowaniem komponentu pozdrow (klasa PozdrowAction) nie zmieniałem w aplikacji i podczas jej uruchomienia żadnej zmiany - PozdrowAction nie było rozpoznane jako komponent seamowy.
 [ServletContextListener] Welcome to Seam 2.0.3.CR1
[Initialization] Namespace: http://jboss.com/products/seam/international, package: org.jboss.seam.international, prefix: org.jboss.seam.international
[Initialization] Namespace: http://jboss.com/products/seam/security, package: org.jboss.seam.security, prefix: org.jboss.seam.security
[Initialization] Namespace: http://jboss.com/products/seam/persistence, package: org.jboss.seam.persistence, prefix: org.jboss.seam.persistence
[Initialization] Namespace: http://jboss.com/products/seam/core, package: org.jboss.seam.core, prefix: org.jboss.seam.core
[Initialization] Namespace: http://jboss.com/products/seam/captcha, package: org.jboss.seam.captcha, prefix: org.jboss.seam.captcha
[Initialization] Namespace: http://jboss.com/products/seam/async, package: org.jboss.seam.async, prefix: org.jboss.seam.async
[Initialization] Namespace: http://jboss.com/products/seam/drools, package: org.jboss.seam.drools, prefix: org.jboss.seam.drools
[Initialization] Namespace: http://jboss.com/products/seam/mail, package: org.jboss.seam.mail, prefix: org.jboss.seam.mail
[Initialization] Namespace: http://jboss.com/products/seam/transaction, package: org.jboss.seam.transaction, prefix: org.jboss.seam.transaction
[Initialization] Namespace: http://jboss.com/products/seam/web, package: org.jboss.seam.web, prefix: org.jboss.seam.web
[Initialization] Namespace: http://jboss.com/products/seam/theme, package: org.jboss.seam.theme, prefix: org.jboss.seam.theme
[Initialization] Namespace: http://jboss.com/products/seam/navigation, package: org.jboss.seam.navigation, prefix: org.jboss.seam.navigation
[Initialization] Namespace: http://jboss.com/products/seam/bpm, package: org.jboss.seam.bpm, prefix: org.jboss.seam.bpm
[Initialization] Namespace: http://jboss.com/products/seam/framework, package: org.jboss.seam.framework, prefix: org.jboss.seam.core.framework
[Initialization] Namespace: http://jboss.com/products/seam/jms, package: org.jboss.seam.jms, prefix: org.jboss.seam.jms
[Initialization] reading /WEB-INF/components.xml
[Initialization] reading
jar:file:/C:/geronimo/repository/pl/jaceklaskowski/seam/seam-richfaces-tree/1.0/seam-richfaces-tree-1.0.war \
/WEB-INF/lib/jboss-seam-2.0.3.CR1.jar!/META-INF/components.xml
[Initialization] reading
jar:file:/C:/geronimo/repository/pl/jaceklaskowski/seam/seam-richfaces-tree/1.0/seam-richfaces-tree-1.0.war \
/WEB-INF/lib/jboss-seam-ui-2.0.3.CR1.jar!/META-INF/components.xml
[Initialization] initializing Seam
[Initialization] two components with same name, higher precedence wins: org.jboss.seam.core.expressions
[Initialization] two components with same name, higher precedence wins: org.jboss.seam.web.isUserInRole
[Initialization] two components with same name, higher precedence wins: org.jboss.seam.security.entityPermissionChecker
[Initialization] two components with same name, higher precedence wins: org.jboss.seam.persistence.persistenceProvider
[Initialization] two components with same name, higher precedence wins: org.jboss.seam.core.locale
[Initialization] two components with same name, higher precedence wins: org.jboss.seam.core.resourceLoader
[Initialization] two components with same name, higher precedence wins: org.jboss.seam.transaction.synchronizations
[Initialization] two components with same name, higher precedence wins: org.jboss.seam.core.locale
[Initialization] two components with same name, higher precedence wins: org.jboss.seam.web.userPrincipal
[Initialization] two components with same name, higher precedence wins: org.jboss.seam.web.parameters
[Initialization] two components with same name, higher precedence wins: org.jboss.seam.bpm.businessProcess
[Initialization] two components with same name, higher precedence wins: org.jboss.seam.core.manager
[Initialization] two components with same name, higher precedence wins: org.jboss.seam.security.identity
[Component] Component: org.jboss.seam.core.init, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.core.Init
[Initialization] Installing components...
[Component] Component: org.jboss.seam.async.dispatcher, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.async.ThreadPoolDispatcher
[Component] Component: org.jboss.seam.captcha.captcha, scope: SESSION, type: JAVA_BEAN, class: org.jboss.seam.captcha.Captcha
[Component] Component: org.jboss.seam.captcha.captchaImage, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.captcha.CaptchaImage
[Component] Component: org.jboss.seam.core.ConversationIdGenerator, scope: APPLICATION, type: JAVA_BEAN, class:
org.jboss.seam.core.ConversationIdGenerator
[Component] Component: org.jboss.seam.core.contexts, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.core.Contexts
[Component] Component: org.jboss.seam.core.conversation, scope: CONVERSATION, type: JAVA_BEAN, class: org.jboss.seam.core.Conversation
[Component] Component: org.jboss.seam.core.conversationEntries, scope: SESSION, type: JAVA_BEAN, class: org.jboss.seam.core.ConversationEntries
[Component] Component: org.jboss.seam.core.conversationListFactory, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.core.ConversationList
[Component] Component: org.jboss.seam.core.conversationPropagation, scope: EVENT, type: JAVA_BEAN, class: org.jboss.seam.core.ConversationPropagation
[Component] Component: org.jboss.seam.core.conversationStackFactory, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.core.ConversationStack
[Component] Component: org.jboss.seam.core.events, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.core.Events
[Component] Component: org.jboss.seam.core.expressions, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.faces.FacesExpressions
[Component] Component: org.jboss.seam.core.interpolator, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.core.Interpolator
[Component] Component: org.jboss.seam.core.locale, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.international.Locale
[Component] Component: org.jboss.seam.core.manager, scope: EVENT, type: JAVA_BEAN, class: org.jboss.seam.faces.FacesManager
[Component] Component: org.jboss.seam.core.resourceBundle, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.core.ResourceBundle
[Component] Component: org.jboss.seam.core.resourceLoader, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.faces.ResourceLoader
[Component] Component: org.jboss.seam.core.validators, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.core.Validators
[Component] Component: org.jboss.seam.exception.exceptions, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.exception.Exceptions
[Component] Component: org.jboss.seam.faces.dataModels, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.faces.DataModels
[Component] Component: org.jboss.seam.faces.facesContext, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.faces.FacesContext
[Component] Component: org.jboss.seam.faces.facesMessages, scope: CONVERSATION, type: JAVA_BEAN, class: org.jboss.seam.faces.FacesMessages
[Component] Component: org.jboss.seam.faces.facesPage, scope: PAGE, type: JAVA_BEAN, class: org.jboss.seam.faces.FacesPage
[Component] Component: org.jboss.seam.faces.httpError, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.faces.HttpError
[Component] Component: org.jboss.seam.faces.redirect, scope: CONVERSATION, type: JAVA_BEAN, class: org.jboss.seam.faces.Redirect
[Component] Component: org.jboss.seam.faces.renderer, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.ui.facelet.FaceletsRenderer
[Component] Component: org.jboss.seam.faces.switcher, scope: PAGE, type: JAVA_BEAN, class: org.jboss.seam.faces.Switcher
[Component] Component: org.jboss.seam.faces.uiComponent, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.faces.UiComponent
[Component] Component: org.jboss.seam.faces.validation, scope: EVENT, type: JAVA_BEAN, class: org.jboss.seam.faces.Validation
[Component] Component: org.jboss.seam.framework.currentDate, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.framework.CurrentDate
[Component] Component: org.jboss.seam.framework.currentDatetime, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.framework.CurrentDatetime
[Component] Component: org.jboss.seam.framework.currentTime, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.framework.CurrentTime
[Component] Component: org.jboss.seam.graphicImage.image, scope: EVENT, type: JAVA_BEAN, class: org.jboss.seam.ui.graphicImage.Image
[Component] Component: org.jboss.seam.international.localeSelector, scope: SESSION, type: JAVA_BEAN, class: org.jboss.seam.international.LocaleSelector
[Component] Component: org.jboss.seam.international.messagesFactory, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.international.Messages
[Component] Component: org.jboss.seam.international.timeZone, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.international.TimeZone
[Component] Component: org.jboss.seam.international.timeZoneSelector, scope: SESSION, type: JAVA_BEAN, class:
org.jboss.seam.international.TimeZoneSelector
[Component] Component: org.jboss.seam.mail.mailSession, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.mail.MailSession
[Component] Component: org.jboss.seam.navigation.pages, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.navigation.Pages
[Component] Component: org.jboss.seam.navigation.safeActions, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.navigation.SafeActions
[Component] Component: org.jboss.seam.persistence.persistenceContexts, scope: CONVERSATION, type: JAVA_BEAN, class:
org.jboss.seam.persistence.PersistenceContexts
[Component] Component: org.jboss.seam.persistence.persistenceProvider, scope: STATELESS, type: JAVA_BEAN, class:
org.jboss.seam.persistence.HibernatePersistenceProvider
[Component] Component: org.jboss.seam.security.configurationFactory, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.security.Configuration
[Component] Component: org.jboss.seam.security.entityPermissionChecker, scope: STATELESS, type: JAVA_BEAN, class:
org.jboss.seam.security.HibernateEntityPermissionChecker
[Component] Component: org.jboss.seam.security.facesSecurityEvents, scope: APPLICATION, type: JAVA_BEAN, class:
org.jboss.seam.security.FacesSecurityEvents
[Component] Component: org.jboss.seam.security.identity, scope: SESSION, type: JAVA_BEAN, class: org.jboss.seam.security.Identity
[Component] Component: org.jboss.seam.theme.themeFactory, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.theme.Theme
[Component] Component: org.jboss.seam.theme.themeSelector, scope: SESSION, type: JAVA_BEAN, class: org.jboss.seam.theme.ThemeSelector
[Component] Component: org.jboss.seam.transaction.synchronizations, scope: EVENT, type: JAVA_BEAN, class:
org.jboss.seam.transaction.SeSynchronizations
[Component] Component: org.jboss.seam.transaction.transaction, scope: EVENT, type: JAVA_BEAN, class: org.jboss.seam.transaction.Transaction
[Component] Component: org.jboss.seam.ui.EntityConverter, scope: CONVERSATION, type: JAVA_BEAN, class: org.jboss.seam.ui.converter.EntityConverter
[Component] Component: org.jboss.seam.ui.entityIdentifierStore, scope: PAGE, type: JAVA_BEAN, class:
org.jboss.seam.ui.converter.entityConverter.EntityIdentifierStore
[Component] Component: org.jboss.seam.ui.entityLoader, scope: STATELESS, type: JAVA_BEAN, class:
org.jboss.seam.ui.converter.entityConverter.EntityLoader
[Component] Component: org.jboss.seam.ui.facelet.faceletCompiler, scope: APPLICATION, type: JAVA_BEAN, class:
org.jboss.seam.ui.facelet.FaceletCompiler
[Component] Component: org.jboss.seam.ui.graphicImage.graphicImageResource, scope: APPLICATION, type: JAVA_BEAN, class:
org.jboss.seam.ui.graphicImage.GraphicImageResource
[Component] Component: org.jboss.seam.ui.graphicImage.graphicImageStore, scope: SESSION, type: JAVA_BEAN, class:
org.jboss.seam.ui.graphicImage.GraphicImageStore
[Component] Component: org.jboss.seam.ui.hibernateEntityLoader, scope: STATELESS, type: JAVA_BEAN, class:
org.jboss.seam.ui.converter.entityConverter.HibernateEntityLoader
[Component] Component: org.jboss.seam.ui.resource.safeStyleResources, scope: APPLICATION, type: JAVA_BEAN, class:
org.jboss.seam.ui.resource.SafeStyleResources
[Component] Component: org.jboss.seam.ui.resource.styleResource, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.ui.resource.StyleResource
[Component] Component: org.jboss.seam.ui.resource.webResource, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.ui.resource.WebResource
[Component] Component: org.jboss.seam.web.exceptionFilter, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.web.ExceptionFilter
[Component] Component: org.jboss.seam.web.isUserInRole, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.faces.IsUserInRole
[Component] Component: org.jboss.seam.web.loggingFilter, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.web.LoggingFilter
[Component] Component: org.jboss.seam.web.multipartFilter, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.web.MultipartFilter
[Component] Component: org.jboss.seam.web.parameters, scope: STATELESS, type: JAVA_BEAN, class: org.jboss.seam.faces.Parameters
[Component] Component: org.jboss.seam.web.redirectFilter, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.web.RedirectFilter
[Component] Component: org.jboss.seam.web.servletContexts, scope: EVENT, type: JAVA_BEAN, class: org.jboss.seam.web.ServletContexts
[Component] Component: org.jboss.seam.web.session, scope: SESSION, type: JAVA_BEAN, class: org.jboss.seam.web.Session
[Component] Component: org.jboss.seam.web.userPrincipal, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.faces.UserPrincipal
[Contexts] starting up: org.jboss.seam.navigation.pages
[Pages] no pages.xml file found: /WEB-INF/pages.xml
[Contexts] starting up: org.jboss.seam.security.facesSecurityEvents
[Initialization] done initializing Seam
[MyfacesConfig] No context init parameter 'org.apache.myfaces.PRETTY_HTML' found, using default value true
[MyfacesConfig] No context init parameter 'org.apache.myfaces.ALLOW_JAVASCRIPT' found, using default value true
[MyfacesConfig] No context init parameter 'org.apache.myfaces.READONLY_AS_DISABLED_FOR_SELECTS' found, using default value true
[MyfacesConfig] No context init parameter 'org.apache.myfaces.RENDER_VIEWSTATE_ID' found, using default value true
[MyfacesConfig] No context init parameter 'org.apache.myfaces.STRICT_XHTML_LINKS' found, using default value true
[MyfacesConfig] No context init parameter 'org.apache.myfaces.CONFIG_REFRESH_PERIOD' found, using default value 2
[MyfacesConfig] No context init parameter 'org.apache.myfaces.VIEWSTATE_JAVASCRIPT' found, using default value false
[MyfacesConfig] Tomahawk jar not available. Autoscrolling, DetectJavascript, AddResourceClass and CheckExtensionsFilter are disabled now.
[MyfacesConfig] Starting up Tomahawk on the MyFaces-JSF-Implementation
[AbstractFacesInitializer] ServletContext
'C:\geronimo\repository\pl\jaceklaskowski\seam\seam-richfaces-tree\1.0\seam-richfaces-tree-1.0.war\' initialized.
Wszystkie komponenty rozpoznane, tylko nie mój pozdrow. Stworzyłem nawet bardzo podstawowy plik /WEB-INF/components.xml, sądząc, że to właśnie jego brak powoduje brak rozpoznania komponentu pozdrow. Jak można zauważyć wczytanie pliku components.xml faktycznie następowało, ale już aktywacja komponentu pozdrow nie.
 <?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
xmlns:core="http://jboss.com/products/seam/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.0.xsd
http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.0.xsd">

<core:init debug="true"/>

</components>
Okazało się, że bez lektury dokumentacji seamowej Chapter 5. Configuring Seam components się nie obędzie. I warto było! Dużo wiedzy w przeciągu niecałych 5 minut! 5 minut za cenę mojego 30-minutowego rozpracowywania, co jest nie tak, warte jest swojej ceny.

Plikiem konfiguracyjnym komponentów seamowych jest components.xml, w którym deklaruję komponenty seamowe i ich parametry (coś ala faces-config.xml w JSF, beans.xml czy applicationContext.xml w Spring Framework, web.xml w aplikacjach webowych, czy...tu wpisz plik konfiguracyjny komponentów swojego ulubionego szkieletu aplikacyjnego). Plik components.xml poszukiwany jest w następujących katalogach aplikacji - WEB-INF, META-INF oraz pliku jar z komponentami zdefiniowanymi przez @Name.

Próbowałem zastosować regułę:

Usually, Seam components are installed when the deployment scanner discovers a class with a @Name annotation sitting in an archive with a seam.properties file or a META-INF/components.xml file.

Próby z seam.properties spełzły na niczym. Żadnej zmiany - wykonanie komponentu pozdrow wciąż kończyło się pustą stroną, której źródło jest następujące:
 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Pozdrowienia</title>
</head>
<body>
<f:view>
#{pozdrow.pozdrowienie}
</f:view>
</body>
</html>
Nie jest to strona oparta o Facelets. Po prostu zwykła strona JSF. Nic nadzwyczajnego dla osób zaznajomionych z tą technologią. Wciąż jednak zamiast #{pozdrow.pozdrowienie} wyświetlany był pusty ciąg znaków, czyli nie wykonał się komponent pozdrow (bo i jak miał się wykonać, skoro Seam nie rozpoznał klasy PozdrowAction jako definicję komponentu pozdrow?!).

Skoro można polegać na automatycznym rozpoznaniu klasy jako komponentu seamowego przez zastosowanie adnotacji @Name (co w moim przypadku nie działało) oraz możliwości zdefiniowania komponentu w pliku components.xml, nie pozostaje mi nic innego jak zastosować drugą możliwość. I to było to!

Plik /WEB-INF/components.xml prezentuje się następująco:
 <?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
xmlns:core="http://jboss.com/products/seam/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.0.xsd
http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.0.xsd">

<core:init debug="true"/>

<component name="pozdrow"
class="pl.jaceklaskowski.seam.PozdrowAction">
</component>

</components>
Uruchomienie aplikacji na Geronimo 2.1.1 to:
 [ServletContextListener] Welcome to Seam 2.0.3.CR1
...
[Initialization] reading /WEB-INF/components.xml
...
[Component] Component: pozdrow, scope: EVENT, type: JAVA_BEAN, class: pl.jaceklaskowski.seam.PozdrowAction
I to jest właśnie komunikat, na który czekałem! Mój komponent seamowy został rozpoznany przez Seama. Wykorzystanie komponentu na stronie pozdrow.xhtml to wykonanie strony http://localhost:8080/seam-richfaces-tree/pozdrow.seam.

co na konsoli Geronimo przedstawione zostało jako:
 [PozdrowAction] Inicjacja PodrowAction za nami
Dokładnie tak, jak sobie tego życzyłem. Temat rozwiązany. Pora zabrać się za coś poważniejszego w Seamie. Tylko, co miałoby to być? Na pewno coś w stylu tworzenia aplikacji seamowej z Groovy. Już nie mogę doczekać się tej konfiguracji. A później pozostanie stworzyć archetyp seamowy z przedstawioną dotychczas konfiguracją. To lubię! ;-)

Pytanie konkursowe: Jaki plik konfiguracyjny w JBoss Seam definiuje komponenty seamowe?