10. Wersja 5 – Aplikacja internetowa PAM / JSF
10.1. Architektura aplikacji
Architektura aplikacji internetowej PAM będzie wyglądała następująco:
![]() |
W tej wersji serwer Glassfish będzie obsługiwał wszystkie warstwy aplikacji:
- warstwa [web] jest obsługiwana przez kontener serwletów serwera (1 poniżej)
- pozostałe warstwy [metier, DAO, jpa] są hostowane przez kontener EJB3 serwera (2 poniżej)
![]() |
Elementy [metier, DAO] aplikacji działającej w kontenerze EJB3 zostały już opisane w aplikacji klient-serwer omówionej w punkcie 7.1, której architektura wyglądała następująco:
![]() |
Warstwy [metier, DAO] działały w kontenerze EJB3 serwera Glassfish, a warstwa [ui] w aplikacji konsolowej lub Swing na innym komputerze:
![]() |
W architekturze nowej aplikacji:
![]() |
należy zapisać jedynie warstwę [web / jsf]. Pozostałe warstwy [metier, DAO, jpa] są już uwzględnione.
W dokumencie [ref3] pokazano, że aplikacja internetowa, w której warstwa internetowa jest zaimplementowana przy użyciu Java Server Faces, ma architekturę podobną do poniższej:
![]() |
Architektura ta realizuje wzorzec projektowy MVC (Model, Widok, Kontroler). Przetwarzanie żądania od klienta przebiega w następujący sposób:
Jeśli żądanie jest wysyłane za pomocą GET, wykonywane są dwa następujące etapy:
- żądanie – przeglądarka klienta wysyła żądanie do kontrolera [Faces Servlet]. Kontroler ten obsługuje wszystkie żądania klientów. Stanowi on punkt wejścia do aplikacji. Jest to element „C” w skrócie MVC.
- odpowiedź – kontroler C zleca wyświetlenie wybranej strony JSF. Jest to widok, czyli „V” w nazwie MVC. Strona JSF wykorzystuje szablon M do zainicjowania dynamicznych części odpowiedzi, którą ma wysłać do klienta. Szablon ten jest klasą Java, która może odwoływać się do warstwy [métier] [4a] w celu dostarczenia widokowi V potrzebnych danych.
Jeśli żądanie jest wysyłane za pomocą POST, między żądaniem a odpowiedzią pojawiają się dwa dodatkowe etapy:
- żądanie – przeglądarka klienta wysyła żądanie do kontrolera [Faces Servlet].
- przetwarzanie – kontroler C przetwarza to żądanie. Żądanie POST zawiera bowiem dane, które należy przetworzyć. W tym celu kontroler korzysta z pomocy menedżerów zdarzeń specyficznych dla aplikacji [2a]. Menedżery te mogą potrzebować warstwy biznesowej [2b]. Menedżer zdarzenia może być zmuszony do aktualizacji niektórych modeli M [2c]. Po przetworzeniu żądania klienta może ono wywołać różne odpowiedzi. Klasycznym przykładem jest:
- strona z komunikatami o błędach, jeśli żądania nie udało się poprawnie przetworzyć
- w przeciwnym razie strona potwierdzenia
Obsługa zdarzenia zwraca do kontrolera [Faces Servlet] wynik w postaci ciągu znaków zwanego kluczem nawigacyjnym.
- nawigacja – kontroler wybiera stronę JSF (= widok), która ma zostać wysłana do klienta. Wybór ten opiera się na kluczu nawigacyjnym zwróconym przez menedżera zdarzeń.
- odpowiedź – wybrana strona JSF wyśle odpowiedź do klienta. Wykorzystuje ona swój szablon M do zainicjowania swoich części dynamicznych. Szablon ten może również odwoływać się do warstwy [métier] [4a] w celu dostarczenia stronie JSF potrzebnych jej danych.
W projekcie JSF:
- kontrolerem C jest serwlet [javax.faces.webapp.FacesServlet]. Znajduje się on w bibliotece [jsf-api.jar].
- Widoki V są zaimplementowane przez strony JSF.
- modele M i menedżery zdarzeń są zaimplementowane przez klasy Java, często nazywane „backing beans”.
- W wersjach JSF i 1.x definicje beanów oraz reguły nawigacji między stronami są określone w pliku [faces-config.xml]. Znajduje się tam lista widoków oraz reguły przejścia między nimi. Począwszy od wersji JSF 2, definicje beanów można tworzyć za pomocą adnotacji, a przejścia między stronami można realizować „na stałe” w kodzie beanów.
10.2. Działanie aplikacji
Po pierwszym wywołaniu aplikacji wyświetla się następująca strona:
![]() |
Następnie wypełnia się formularz, a potem wyświetla się wysokość wynagrodzenia:
![]() |
Otrzymujemy następujący wynik:
![]() |
Ta wersja oblicza fikcyjne wynagrodzenie. Nie należy zwracać uwagi na treść strony, ale na jej formatowanie. Po kliknięciu przycisku [Raz] następuje powrót do strony [A].
Błędne wpisy są sygnalizowane, jak pokazuje poniższy przykład:
![]() |
10.3. Projekt NetBeans
Stworzymy pierwszą wersję aplikacji, w której warstwa [métier] będzie symulowana. Będziemy mieli następującą architekturę:
![]() |
Gdy menedżery zdarzeń lub modele będą żądać danych od warstwy [métier] [2b, 4a], ta dostarczy im dane fikcyjne. Celem jest uzyskanie warstwy internetowej prawidłowo reagującej na żądania użytkownika. Gdy to osiągniemy, pozostanie nam jedynie zainstalować warstwę serwerową opracowaną w punkcie 7.1:
![]() |
Będzie to wersja 2 internetowej wersji naszej aplikacji PAM.
Projekt NetBeans dla wersji 1 to następujący projekt Maven:
![]() |
- w [1], pliki konfiguracyjne
- w [2], strony w XHTML oraz arkusz stylów
- w [3], klasy warstwy w [web]
- w pliku [4], obiekty wymieniane między warstwą [web] a warstwą [métier] oraz samą warstwą [métier]
- w [5], plik komunikatów dotyczących internacjonalizacji aplikacji
- w [6] – zależności aplikacji
Przyjrzyjmy się niektórym z tych elementów.
10.3.1. Pliki konfiguracyjne
Plik [web.xml] jest generowany domyślnie przez NetBeans i zawiera dodatkowo konfigurację strony wyjątków:
- wiersz 30: [index.html] to strona główna aplikacji
- wiersze 32–39: konfiguracja strony wyjątku
Strona [exception.html] pochodzi ze strony [ref3]. Jej kod wygląda następująco:
Każdy wyjątek, który nie jest wyraźnie obsługiwany przez kod aplikacji internetowej, spowoduje wyświetlenie strony podobnej do poniższej:
![]() |
Plik [faces-config.xml] będzie wyglądał następująco:
Należy zwrócić uwagę na następujące kwestie:
- wiersze 9–14: plik [messages.properties] zostanie wykorzystany do internacjonalizacji stron. Będzie on dostępny na stronach XHTML za pośrednictwem klucza msg.
- wiersz 15: określa plik [messages.properties] jako plik, który ma być przeszukiwany w pierwszej kolejności w przypadku komunikatów o błędach wyświetlanych przez tagi <h:messages> i <h:message>. Pozwala to na zmianę niektórych domyślnych komunikatów o błędach z pliku JSF. Możliwość ta nie jest tutaj wykorzystywana.
10.3.2. Arkusz stylów
Plik [styles.css] ma następującą treść:
.libelle{
background-color: #ccffff;
font-family: 'Times New Roman',Times,serif;
font-size: 14px;
font-weight: bold
}
body{
background-color: #ffccff
}
.error{
color: #ff3333
}
.info{
background-color: #99cc00
}
.titreInfos{
background-color: #ffcc00
}
Oto przykłady kodu JSF wykorzystujące te style:
| |
| |
| ![]() |
10.3.3. Plik komunikatów
Plik komunikatów [messages_fr.properties] ma następującą treść:
form.titre=Feuille de salaire
form.comboEmployes.libell\u00e9=Employ\u00e9
form.heuresTravaill\u00e9es.libell\u00e9=Heures travaill\u00e9es
form.joursTravaill\u00e9s.libell\u00e9=Jours travaill\u00e9s
form.heuresTravaill\u00e9es.required=Indiquez le nombre d'heures travaill\u00e9es
form.heuresTravaill\u00e9es.validation=Donn\u00e9e incorrecte
form.joursTravaill\u00e9s.required=Indiquez le nombre de jours travaill\u00e9s
form.joursTravaill\u00e9s.validation=Donn\u00e9e incorrecte
form.btnSalaire.libell\u00e9=Salaire
form.btnRaz.libell\u00e9=Raz
exception.header=L'exception suivante s'est produite
exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.requestUri=Url demand\u00e9e lors de l'erreur
exception.servletName=Nom de la servlet demand\u00e9e lorsque l'erreur s'est produite
form.infos.employ\u00e9=Informations Employ\u00e9
form.employe.nom=Nom
form.employe.pr\u00e9nom=Pr\u00e9nom
form.employe.adresse=Adresse
form.employe.ville=Ville
form.employe.codePostal=Code postal
form.employe.indice=Indice
form.infos.cotisations=Informations Cotisations sociales
form.cotisations.csgrds=CSGRDS
form.cotisations.csgd=CSGD
form.cotisations.retraite=Retraite
form.cotisations.secu=S\u00e9curit\u00e9 sociale
form.infos.indemnites=Informations Indemnit\u00e9s
form.indemnites.salaireHoraire=Salaire horaire
form.indemnites.entretienJour=Entretien / Jour
form.indemnites.repasJour=Repas / Jour
form.indemnites.cong\u00e9sPay\u00e9s=Cong\u00e9s pay\u00e9s
form.infos.salaire=Informations Salaire
form.salaire.base=Salaire de base
form.salaire.cotisationsSociales=Cotisations sociales
form.salaire.entretien=Indemnit\u00e9s d'entretien
form.salaire.repas=Indemnit\u00e9s de repas
form.salaire.net=Salaire net
Wszystkie te komunikaty są wykorzystywane na stronie [index.xhtml], z wyjątkiem tych z wierszy 11–15, które są wykorzystywane na stronie [exception.xhtml].
10.3.4. Zakres działania beanów
Bean [web.forms.Form] będzie miał zakres request:
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
@ManagedBean
@RequestScoped
public class Form implements Serializable {
Bean [web.utils.ChangeLocale] będzie miał zakres application:
package web.utils;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
// ustawienia regionalne stron
private String locale="fr";
public ChangeLocale() {
}
public String setFrenchLocale(){
locale="fr";
return null;
}
public String setEnglishLocale(){
locale="en";
return null;
}
public String getLocale() {
return locale;
}
public void setLocale(String locale) {
this.locale = locale;
}
}
10.3.5. Warstwa [métier]
Warstwa [métier] implementuje następujący interfejs IMetierLocal:
package metier;
import java.util.List;
import javax.ejb.Local;
import jpa.Employe;
@Local
public interface IMetierLocal {
// pobierz listę płac
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
// lista pracowników
List<Employe> findAllEmployes();
}
Interfejs ten jest wykorzystywany w części serwerowej aplikacji klient-serwer opisanej w paragrafie 7.1.
Klasa Metier, której użyjemy do przetestowania warstwy [web], implementuje ten interfejs w następujący sposób:
package metier;
...
public class Metier implements IMetierLocal {
// słownik pracowników indeksowany według numeru SS
private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
// lista pracowników
private List<Employe> listEmployes;
// pobierz listę płac
public FeuilleSalaire calculerFeuilleSalaire(String SS,
double nbHeuresTravaillées, int nbJoursTravaillés) {
// pobieranie pracownika o numerze SS
Employe e=hashEmployes.get(SS);
// zwracamy fikcyjny arkusz płacowy
return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),new ElementsSalaire(100,100,100,100,100));
}
// lista pracowników
public List<Employe> findAllEmployes() {
if(listEmployes==null){
// tworzy się listę dwóch pracowników
listEmployes=new ArrayList<Employe>();
listEmployes.add(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",new Indemnite(2,2.1,2.1,3.1,15)));
listEmployes.add(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",new Indemnite(1,1.93,2,3,12)));
// słownik pracowników indeksowany według numeru SS
for(Employe e:listEmployes){
hashEmployes.put(e.getSS(),e);
}
}
// zwracamy listę pracowników
return listEmployes;
}
}
Rozszyfrowanie tego kodu pozostawiamy czytelnikowi. Należy zwrócić uwagę na zastosowaną metodę: aby uniknąć konieczności wdrażania części aplikacji o nazwie EJB, symulujemy warstwę [métier]. Gdy warstwa [web] zostanie uznana za poprawną, będziemy mogli zastąpić ją prawdziwą warstwą [métier].
10.4. Formularz [index.xhtml] i jego szablon [Form.java]
Teraz tworzymy stronę XHTML formularza oraz jej szablon.
Zalecane materiały do przeczytania w [ref3]:
- przykład nr 3 (mv-jsf2-03) dotyczący listy tagów, których można używać w formularzu
- przykład nr 4 (mv-jsf2-04) dotyczący list rozwijanych wypełnianych przez szablon
- przykład nr 6 (mv-jsf2-06) dotyczący walidacji danych wprowadzonych przez użytkownika
- przykład nr 7 (mv-jsf2-07) dotyczący obsługi przycisku [Raz]
10.4.1. krok 1
Zadanie: Utwórz formularz [index.xhtml] oraz jego model [Form.java], niezbędne do wygenerowania następującej strony:
![]() |
Elementy do wprowadzania danych są następujące:
id | typ JSF | szablon | rola | |
comboEmployes | <h:selectOneMenu> | Ciąg znaków comboEmployesValue List<Pracownik> getEmployes() | zawiera listę pracowników w postaci „imię nazwisko”. | |
heuresTravaillees | <h:inputText> | Ciąg znaków heuresTravaillées | liczba przepracowanych godzin – liczba rzeczywista | |
joursTravailles | <h:inputText> | Ciąg znaków joursTravaillés | liczba przepracowanych dni – liczba całkowita | |
btnSalaire | <h:commandButton> | uruchamia obliczanie wynagrodzenia | ||
btnRaz | <h:commandButton> | przywraca formularz do stanu początkowego |
- metoda getEmployes zwróci listę pracowników, którą uzyska z warstwy [métier]. Obiekty wyświetlane przez listę rozwijaną będą miały jako atrybut itemValue numer pracownika SS, a jako atrybut itemLabel ciąg znaków składający się z imienia i nazwiska pracownika.
- Przyciski [Salaire] i [Raz] nie będą na razie powiązane z procedurami obsługi zdarzeń.
- Sprawdzana będzie poprawność wprowadzonych danych.

Przetestuj tę wersję. Sprawdź w szczególności, czy błędy wprowadzania danych są prawidłowo sygnalizowane.
Uwaga: ważne jest, aby atrybuty id elementów strony nie zawierały znaków akcentowanych. W przypadku Glassfish 3.1.2 powoduje to awarię aplikacji.
10.4.2. Krok 2
Zadanie: wypełnij formularz [index.xhtml] i jego szablon [Form.java], aby po kliknięciu przycisku [Salaire] wyświetliła się następująca strona:
![]() |
Przycisk [Salaire] zostanie powiązany z menedżerem zdarzeń calculerSalaire w szablonie. Metoda ta będzie korzystać z metody calculerFeuilleSalaire z warstwy [métier]. Ten arkusz płacowy zostanie sporządzony dla pracownika wybranego w [1].
W szablonie list płac będzie reprezentowany przez następujące pole prywatne:
private FeuilleSalaire feuilleSalaire;
posiadające metody get i set.
Aby uzyskać informacje zawarte w tym obiekcie, na stronie JSF można wpisać wyrażenia takie jak poniższe:
<h:outputText value="#{form.feuilleSalaire.employe.nom}"/>
Wyrażenie atrybutu „value” zostanie obliczone w następujący sposób:
[form].getFeuilleSalaire().getEmploye().getNom(), gdzie [form] reprezentuje instancję klasy [Form.java]. Czytelnik może sprawdzić, czy metody get użyte w tym miejscu rzeczywiście istnieją odpowiednio w klasach [Form], [FeuilleSalaire] i [Employe]. Gdyby tak nie było, podczas oceny wyrażenia zostałby zgłoszony wyjątek.
Przetestuj tę nową wersję.
10.4.3. Krok 3
Zadanie: uzupełnij formularz [index.xhtml] oraz jego szablon [Form.java], aby uzyskać następujące dodatkowe informacje:
![]() |
Postępujemy tak samo jak poprzednio. Pojawia się jednak problem z symbolem waluty euro, który występuje na przykład w [1]. W przypadku aplikacji międzynarodowej lepiej byłoby mieć format wyświetlania i znak waluty z używanego pliku locale (en, de, fr, ...). Można to uzyskać w następujący sposób:
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
</h:outputFormat>
Można by zapisać:
<h:outputText value="#{form.feuilleSalaire.employe.indemnite.entretienJour} є">
ale przy ustawieniu regionalnym en_GB (angielski GB) nadal wyświetlałaby się kwota w euro, podczas gdy należałoby użyć funta brytyjskiego (£). Tag <h:outputFormat> pozwala wyświetlać informacje w zależności od wartości locale na wyświetlanej stronie JSF:
- wiersz 1: wyświetla parametr {0}, który jest liczbą (number) reprezentującą kwotę pieniężną (currency)
- wiersz 2: tag <f:param> nadaje wartość parametrowi {0}. Drugi tag <f:param> nadałby wartość parametrowi oznaczonemu jako {1} i tak dalej.
10.4.4. krok 4
Materiały do przeczytania: przykład nr 7 (mv-jsf2-07) w [ref3].
Zadanie: uzupełnij formularz [index.xhtml] oraz jego szablon [Form.java], aby obsłużyć przycisk [Raz].
Przycisk [Raz] przywraca formularz do stanu, w jakim znajdował się w momencie pierwszego wywołania za pomocą GET. W tym przypadku pojawia się kilka trudności. Niektóre z nich zostały wyjaśnione w [ref3].
Formularz wygenerowany przez przycisk [Raz] nie obejmuje całego formularza, a jedynie jego część saisie:

Wynik ten można uzyskać za pomocą tagu <f:subview> użytego w następujący sposób:
<f:subview id="viewInfos" rendered="#{form.viewInfosIsRendered}">
... la partie du formulaire qu'on veut pouvoir ne pas afficher
</f:subview>
Tag <f:subview> otacza całą część formularza, która może być wyświetlana lub ukrywana. Każdy komponent można wyświetlić lub ukryć za pomocą atrybutu rendered. Jeśli rendered="true", komponent jest wyświetlany, a jeśli rendered="false", nie jest wyświetlany. Jeśli atrybut rendered przyjmuje określoną wartość w szablonie, wyświetlanie komponentu można kontrolować programowo.
W powyższym przykładzie wyświetlanie widoku viewInfos będzie kontrolowane za pomocą następującego pola:
private boolean viewInfosIsRendered;
wraz z metodami get i set. Metody obsługujące kliknięcia przycisków [Salaire] i [Raz] zaktualizują tę wartość logiczną w zależności od tego, czy widok viewInfos ma zostać wyświetlony, czy nie.

















