Pokazywanie postów oznaczonych etykietą jboss seam. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą jboss seam. Pokaż wszystkie posty

02 lipca 2008

Umiędzynarodowienie w JBoss Seam, NetCAT 6.5 oraz nowa grupa oferty-pracy-java

6 komentarzy
W końcu natrafiłem na notkę odnośnie roli seam.properties w aplikacji w książce Beginning JBoss® Seam: From Novice to Professional wydawnictwa Apress oraz gdzieś w Sieci. Cała magia seam.properties to przede wszystkim oznaczenie archiwum WAR lub JAR jako zawierające komponenty seamowe i stąd konieczność umieszczenia go w /WEB-INF/classes w aplikacji webowej, aby Seam zechciał rozważyć przeszukiwanie aplikacji w celu "namierzenia" klas oznaczonych adnotacją @Name. Plik seam.properties może być całkowicie pusty - wystarczy jego istnienie. Trudno jednak zrozumieć, dlaczego umieszczenie definicji komponentów w components.xml działa, a już przeszukiwanie ich w archiwum nie. Czyż Seam nie dowiedział się właśnie poprzez plik components.xml, że ma do czynienia z aplikacją seamową?! Na chwilę obecną nie zamierzam zaglądać do kodu źródłowego, ale pewnie tam należałoby szukać odpowiedzi. Chętni?! ;-)

Dzisiaj na tapetę poszedł temat umiędzynarodowienia aplikacji seamowej (przypomina mi się temat Umiędzynarodowienie w Apache Wicket i trochę ckni mi się do Wicketa i możliwości jego uruchomienia na OSGi). Temat umiędzynarodowienia aplikacji korzystającej z JavaServer Faces był już przeze mnie przedstawiany w artykułach requiredMessage i resource-bundle - udoskonalona kontrola komunikatów w JSF 1.2, Uruchamiamy pierwszą aplikację w technologii JavaServer Faces, Tworzenie aplikacji z JavaServer Faces, Apache Maven i Apache Geronimo czy JavaServer Faces i Spring Framework w parze z Apache Maven i Apache Geronimo. Wszystkie dotykają tematu użycia message-bundle w faces-config.xml, kontrolki <f:loadBundle> na stronie JSF, gdzie korzysta się z tłumaczeń oraz wskazanie na niego z danego pliku tłumaczeń - #{komunikaty['wprowadz_imie']}. Jest tego trochę do zapamiętania, co Seam zauważalnie skrócił.

Pierwsza różnica między "czystą" (nieseamową) aplikacją JSF a opartą o Seama to założenie, że wszystkie pliki properties z tłumaczeniami składają się na jedną mapę messages klucz-tłumaczenie. W przypadku JSF mamy mapę per plik tłumaczeń. Kolejna zmiana to zniesienie obowiązku definiowania plików tłumaczeń w faces-config.xml - obowiązkowym pliku konfiguracyjnym każdej aplikacji opartej o JSF. I na koniec podsumowania różnic, Seam znosi obowiązek deklarowania użycia pliku tłumaczeń za pomocą <f:loadBundle>. Z Seamem wystarczy utworzyć pojedyńczy plik messages.properties dla wybranych języków i użyć konstrukcji #{messages['klucz']} w dowolnym miejscu strony JSF. Oczywiście istnieje możliwość zdefiniowania wielu plików tłumaczeń. "Gdzie?" - zapytasz. Oczywiście w opcjonalnym components.xml, który wraz z kilkoma innymi opcjonalnymi plikami xmlowymi przejął rolę obowiązkowego faces-config.xml w JSF (podkreślam użycie słów opcjonalny i obowiązkowy).

Więcej informacji ze źródła o umiędzynarodowieniu aplikacji seamowych w dokumentacji Seama - Chapter 15. Internationalization, localization and themes, a w szczególności 15.3.1. Defining labels. Podczas poznawania mechanizmu tłumaczeń w Seamie brakowało mi wiedzy, którą ostatecznie udało mi się znaleść na forum Seam Users w wątku Including a resource bundle not called messages*.

Umiędzynarodowienie mojej aplikacji seamowej, którą rozwijam od kilku dni, rozpocząłem od zmian w pliku components.xml, gdzie wskażę pliki tłumaczeń.
 <?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" />
<core:resource-loader>
<core:bundle-names>
<value>komunikaty</value>
<value>messages</value>
</core:bundle-names>
</core:resource-loader>
</components>
Przypomnę, że jest to krok opcjonalny i domyślnie Seam poszukuje pliku messages.properties dla danego języka, np. messages_pl.properties dla polskiego. Plik musi znajdować się w ścieżce klas aplikacji, co w moim przypadku aplikacji webowej sprowadziło się do umieszczenia go w katalogu /WEB-INF/classes. W powyższej konfiguracji w components.xml dodałem również użycie pliku komunikaty_pl.properties.

Opcjonalnie zdefiniowałem domyślny język aplikacji (polski) i inne wspierane języki (polski) w faces-config.xml:
 <?xml version='1.0' encoding='UTF-8'?>
<faces-config version="1.2"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
<application>
<locale-config>
<default-locale>pl</default-locale>
<supported-locale>pl</supported-locale>
</locale-config>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
</faces-config>
W ten sposób zagwarantowałem, że jedynym słusznym językiem w aplikacji będzie polski. Wyboru nie ma.

Zawartość pliku messages_pl.properties:
 kategoria.tytul=Administracja kategoriami
oraz komunikaty_pl.properties:
 kategoria.nazwa=Nazwa
kategoria.opis=Opis
Oba pliki muszą znaleźć się na ścieżce klas, np. /WEB-INF/classes w ramach archiwum war.

Mając tak zdefiniowane pliki tłumaczeń zmodyfikowałem stronę 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>#{messages['kategoria.tytul']}</title>
</head>
<body>
<f:view>
<h:form>
<s:validateAll>
<h:panelGrid columns="2">
#{messages['kategoria.nazwa']}: <h:inputText value="#{kategoria.nazwa}" required="true" />
#{messages['kategoria.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>
Na uwagę zasługuje użycie #{messages['kategoria.tytul']} w tytule poza sekcją <f:view> oraz #{messages['kategoria.nazwa']} i #{messages['kategoria.opis']} w <h:panelGrid>. Przypomnę, że wszystkie komunikaty trafiają do jednej zbiorczej mapy - messages, więc należy zagwarantować unikalność zawartych w niej kluczy tłumaczeń, np. poprzedzając nazwą strony, w której się znajdują.

Uruchomienie strony kategoria.xhtml, to jak można się domyśleć pojawienie się napisów z plików tłumaczeń. Proste, nieprawdaż?

Na zakończenie kilka słów spoza obszaru Seama. Właśnie pojawiło się zaproszenie do NetBeans IDE 6.5 Community Acceptance Testing program (NetCAT). W programie NetCAT miałem już przyjemność uczestniczyć w poprzednich edycjach i wiele mogłem się w nich nauczyć samego NetBeansa, ale również i technologiach Korporacyjnej Javy recenzując artykuły o nich i samemu sprawdzając ich działanie w NB. I tym razem nie mogłem sobie odpuścić zgłoszenia się do programu, szczególnie, że z tą wersją rokuję pewne nadzieje w kontekście znaczącego usprawnienia we wsparciu dwóch serwerów aplikacyjnych IBM WebSphere Application Server 6.1 oraz Apache Geronimo. Może się do czegoś przydam i sprawię, aby pracowało się przyjmniej z NetBeans 6.5? ;-) A Ty? Każdy może się przyłączyć, więc szkoda czasu na zbędne zastanawianie się - lepiej ten czas przeznaczyć na poznawanie błędów^H^H^Hfunkcjonalności NB 6.5.

Kilka dni temu Michał B. podjął się tematu wyjaśnienia odmiany słowa Java i jego pisowni w komentarzu do W sprawie Recenzja książki "Hibernate. Od Nowicjusza do Profesjonalisty". Odpowiedź od jego znajomego filologa trochę mnie zaskoczyła:

"wyraz 'Java' proponuję używać tylko w formie mianownikowej i odmieniać tylko wyraz 'język' - czyli np. języka Java, o/w języku Java.
To zapewni jednoznaczność terminologii w tekstach - oczywiście mówiąc nie trzymałbym się kurczowo takiej opcji - tu może być sama [dżawa] a formy [w dżawie] itp. wystarczą (o czym sam doskonale wiesz:)) "


Zaskoczeni podobnie jak ja? Jestem zdania, że można odmieniać słowo Java w dowolnym zestawieniu, czyli o javie, z javą i bez javy (chociaż to ostatnie nie przechodzi mi jakoś przez gardło ;-))

I na koniec informacja o dedykowanej grupie oferty-pracy-java, gdzie zamieszczane są wyłącznie oferty dotyczące obszaru programowania, testowania czy projektowania z użyciem technologii javowych z uwzględnieniem widełek płacowych. Jeśli uważasz, że jest coś więcej, co powinno pojawiać się w każdym ogłoszeniu o pracę, zapraszam do udziału w grupie i wprowadzenia stosownych zmian. Każdy ma prawo głosu, a celem jest stworzenie miejsca, w którym oferty będą tak profesjonalne, jak osoby, które z nich zamierzają skorzystać. My jako owa wykwalifikowana kadra możemy wpłynąć na kształt ofert i korzystania z nich dla dobra własnego. W ten sposób z pewnością uda nam się wypracować nowy styl pisania ofert z właściwymi wymaganiami, widełkami płacowymi, dodatkami, itp. Grupa jest moderowana i przy rejestracji należy określić swoją rolę - oferent/rekruter vs zainteresowany (wyłącznie dla celów...hmm...nie mam pojęcia jakich, ale pomyślałem, że dobrze wiedzieć jakie są proporcje). Dyskusje o uszczegółówienie ofert jak najbardziej wskazane. Grupa jest publiczna. Archiwum do przeglądania przez każdego, ale jedynie członkowie mogą dyskutować na grupie (po zatwierdzeniu posta przez moderatora). Sądzę, że warunki są idealne do wymagań obu stron - firm rekrutacyjnych i kadry pracowniczej. Promocja siebie jak najbardziej wskazana przez wysłanie CV na grupę. Wszystko, co sprawi, że będzie można znaleźć swoją wymarzoną pracę łatwiej mile widziane. Zapisz się i działaj!

Pytanie konkursowe: Jak nazywa się mapa tłumaczeń w Seamie? I takie bardzo zaawansowane: W jaki sposób (gdzie i jak) definiuje się wiele plików tłumaczeń w Seamie?

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?

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?

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?