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.PozdrowActionSeam 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;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!):
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;
}
}
<dependency>W przeciwnym przypadku - brak biblioteki hibernate-commons-annotations.jar - wykonanie aplikacji skutkuje zgłoszeniem wyjątku:
<groupId>org.hibernate</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>3.3.0.ga</version>
</dependency>
java.lang.NoClassDefFoundError: org/hibernate/annotations/common/reflection/XMemberTworzę stronę kategoria.xhtml, która zawiera formularz do wprowadzania nowych kategorii.
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)
<?xml version="1.0" encoding="utf-8"?>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.
<!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>
Wspomniany komponent seamowy kategoriaAgent reprezentowany jest przez klasę pl.jaceklaskowski.seam.KategoriaAgent:
package pl.jaceklaskowski.seam;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:
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");
}
}
11:59:36,984 INFO [Version] Hibernate Validator 3.0.0.GADla kompletu informacji prezentuję deskryptor wdrożenia aplikacji webowej - /WEB-INF/web.xml:
11:59:37,134 INFO [KategoriaAgent] Tu nastąpi zapis kategorii "Kategoria #1" do bazy danych
<?xml version="1.0" encoding="UTF-8"?>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?
<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>
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?
Muszę powiedzieć, że pytania z pozoru mogłyby się wydawać proste. Jednak po dłuższym zastanowieniu okazało się, że nie jest tak łatwo :)
OdpowiedzUsuńMoja próba odpowiedzi jest następująca.
W pierwszym przypadku request wyzwala Faces Servlet (bo URL jest zakończony .seam), ale nie jest obsługiwany przez facelets (ponieważ rozszerzenie w URLu nie jest .xhtml jak to definiuje parametr facelets.VIEW_MAPPINGS) a następnie szukany jest odpowiedni plik kategoria.jsp z uwagi na parametr javax.faces.DEFAULT_SUFFIX ustawiony na .jsp, ponieważ takiego nie ma (bo jest kategoria.xhtml), to rzuca 404.
Druga sprawa jest prostsza. Znowu wywoływany jest serwlet Faces Servlet, ponieważ URL pasuje do wzoru /seam/*, jednak z tą różnicą, że już jest obsługiwany przez ten serwlet, ponieważ URL kończy się na .xhtml (facelets.VIEW_MAPPINGS). Odnajduje plik kategoria.xhtml i przetwarza (wraz z Facelets).
Uff. Możesz się odieść do tego Jacku?
Dokładnie jak piszesz. Ave MaGu! ;-)
OdpowiedzUsuńJacek