05 października 2007

Konwertery w JavaServer Faces część 1

Podczas pisania ostatniego artykułu zatytułowanego Kwadrans w Bibliotece z JSF 1.2, EJB 3.0 i JPA skorzystałem z ciekawej funkcjonalności JavaServer Faces 1.2 (JSF) - konwerterów.

Konwerterem nazywamy klasę realizującą interfejs javax.faces.convert.Converter. Interfejs dostarcza dwóch metod:
Za pomocą tych dwóch metod obiekt wykorzystywany w modelu aplikacji JSF może zostać zamieniony na reprezentację znakową, która następnie będzie wykorzystana do zbudowania odpowiedzi, tj. utworzenia strony i na odwrót. JSF dostarcza konwertery dla typów prostych oraz kilku typów opakowujących, np. LongConverter czy DateTimeConverter (pełna lista dostępna jest w dokumentacji javax.faces.convert.Converter).

Postanowiłem przedstawić temat partiami, gdyż próba stworzenia artykułu omawiającego cały temat zaczęła zabierać mi zbyt wiele czasu, a i utrzymywanie wielu wersji stało się wyzwaniem. Dla prostego zadania należy stosować proste rozwiązania, więc potencjalnie bardzo rozległy temat podzieliłem na części, które powinny spełnić oczekiwania i początkujących graczy JSF, jak i zaawansowanych programistów JSF.

Prezentację tematu zacznę od utworzenia trywialnej aplikacji JSF o bardzo wymyślnej nazwie Konwerter. Aplikacja składa się z pojedyńczej strony JSF - strona1.jsp, w której skorzystam z pojedyńczego ziarna zarządzanego pracownik. Początkujący w JSF mogą potraktować aplikację jako dobry wstęp do tematu JSF, podczas gdy zaawansowani jako dobrą rozrywkę.

Zacznę od ziarna zarządzanego pracownik, który reprezentowany jest przez klasę pl.jaceklaskowski.konwerter.Pracownik.

package pl.jaceklaskowski.konwerter;

public class Pracownik {

private long numer;
private String imie;

public Pracownik() {
}

public Pracownik(long numer, String imie) {
this.numer = numer;
this.imie = imie;
}

public long getNumer() {
return numer;
}

public void setNumer(long numer) {
this.numer = numer;
}

public String getImie() {
return imie;
}

public void setImie(String imie) {
System.out.println("setImie(" + imie + ")");
this.imie = imie;
}
}
Ziarna zarządzane JSF definiowane są w pliku faces-config.xml w sekcji managed-bean (chociaż przy wsparciu narzędzi graficznych do tworzenia aplikacji JSF wiedza dotycząca budowy pliku faces-config.xml zapewne potrzebna jest jedynie do egzaminu Sun Certified Web Component Developer (SCWCD), którego nota bene dla Java EE 5 jeszcze nie ma).

<?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">
<managed-bean>
<managed-bean-name>pracownik</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.konwerter.Pracownik</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>
Plik znajduje się w katalogu WEB-INF aplikacji internetowej.

Po zdefiniowaniu ziarna przechodzę do zdefiniowania strony startowej aplikacji JSF - strona1.jsp, która wygląda następująco:

<?xml version='1.0' encoding='UTF-8'?>

<%@page contentType="text/html" pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Mini-aplikacja JSF - konwertery</title>
</head>
<body>
<f:view>
<h:form>
Podaj imię pracownika: <h:inputText value="#{pracownik.imie}" />
<br/>
<h:commandButton value="Zatwierdź" />
</h:form>
</f:view>
</body>
</html>
Do najważniejszych elementów strony należy kontrolka JSF - f:view, która określa zasięg działania servletu FacesServlet odpowiedzialnego za przetwarzanie stron JSP i wykonanie znaczników JSF. Pozostałe kontrolki (w przypadku JSP będą to znaczniki JSF), które zasługują na uwagę to h:inputText oraz h:commandButton. Pierwszy - h:inputText - wyświetla kontrolkę wprowadzania danych przez użytkownika i związuje ją z ziarnem pracownik, a dokładniej z jego właściwością imie. Druga kontrolka - h:commandButton - zatwierdza formularz i uruchamia "infrastrukturę" przetwarzania zlecenia JSF.

Istotną rzeczą w tej niezwykle prostej stronie JSF jest fakt zaangażowania mechanizmu konwersji danych wprowadzonych przez użytkownika przez JSF. Zanim dana zostanie przekazana do ziarna (podczas wywołania metody Pracownik.setImie(String)) najpierw następuje konwersja ciągu znaków ze strony na odpowiedni typ. Typem docelowym jest typ parametru wejściowego związanej metody ustawiającej właściwość i jeśli taka konwersja może zostać obsłużona przez JSF jest dokonywana. W naszym przypadku, typem właściwości imie jest String, więc JSF obsłuży to bez naszej ingerencji i nastąpi automatyczna konwersja z typu znakowego na typ znakowy. I to jest właśnie siła JSF - konwersja nastąpi automatycznie podczas zapisywania danych wprowadzonych przez użytkownika do ziarna zarządzanego i późniejszego odczytania danych z niego, kiedy to odczytana właściwość zostanie zamieniona na ciąg znaków do wyświetlenia na stronie.

Do uruchomienia aplikacji potrzebny jest jeszcze deskryptor aplikacji WEB-INF/web.xml, który stworzyłem, a właściwie stworzono dla mnie automatycznie, podczas tworzenia projektu w NetBeans IDE 6.0. Przedstawiam go jedynie dla kompletności (i przypomnienia dla przygotowujących się do egzaminu SCWCD 5.0).

<?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.sun.faces.verifyObjects</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.validateXml</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</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>/faces/*</url-pattern>
</servlet-mapping>
</web-app>
Pozostaje uruchomić aplikację.

Wciśnięcie przycisku Zatwierdź to wywołanie metody Pracownik.setImie(String), która wypisze wartość parametru wejściowego imie na konsolę serwera aplikacji (w moim przypadku będzie to Glassfish v2, ale może to być i kontener servletów Apache Tomcat).
deployed with moduleid = konwerter
Initializing Sun's JavaServer Faces implementation (1.2_04-b20-p03) for context '/konwerter'
setImie(Jacek)
Gotowy projekt aplikacji Konwerter dostępny jest jako jsf-konwerter-czesc1.zip.