Skip to content

10. Версія 5 — Веб-додаток PAM / JSF

10.1. Архітектура додатка

Архітектура веб-додатку PAM буде такою:

У цій версії сервер Glassfish розміщуватиме всі рівні додатка:

  • шар [web] розміщується в контейнері сервлетів сервера (1 нижче)
  • інші рівні [metier, DAO, jpa] розміщуються у контейнері EJB3 сервера (2 нижче)

Елементи [metier, DAO] додатка, що виконуються в контейнері EJB3, вже були описані в клієнтсько-серверному додатку, розглянутому в розділі 7.1, архітектура якого була такою:

Рівні [metier, DAO] виконувалися в контейнері EJB3 сервера Glassfish, а рівень [ui] — у консольному додатку або додатку Swing на іншій машині:

В архітектурі нового додатка:

потрібно написати лише шар [web / jsf]. Інші шари [metier, DAO, jpa] вже є.

У документі [ref3] показано, що веб-додаток, у якому веб-шар реалізовано за допомогою Java Server Faces, має архітектуру, подібну до наведеної нижче:

Ця архітектура реалізує шаблон проектування MVC (Модель, Вигляд, Контролер). Обробка запиту клієнта відбувається наступним чином:

Якщо запит надходить через GET, виконуються два наступні етапи:

  1. запит — браузер клієнта надсилає запит до контролера [Faces Servlet]. Цей контролер обробляє всі запити клієнтів. Це вхідна точка додатка. Це «C» у MVC.
  2. відповідь — контролер C запитує обрану сторінку JSF, щоб вона відобразилася. Це представлення, «V» у MVC. Сторінка JSF використовує шаблон M для ініціалізації динамічних частин відповіді, яку вона має надіслати клієнту. Цей шаблон — це клас Java, який може звертатися до рівня [métier] [4a], щоб надати представленню V необхідні дані.

Якщо запит надсилається за допомогою POST, між запитом і відповіддю додаються два додаткові етапи:

  1. запит — браузер-клієнт надсилає запит до контролера [Faces Servlet].
  2. обробка — контролер C обробляє цей запит. Адже запит POST супроводжується даними, які потрібно обробити. Для цього контролер використовує допомогу обробників подій, специфічних для написаного додатка [2a]. Цим обробникам можуть знадобитися дані бізнес-шару [2b]. Обробник події може бути змушений оновити певні моделі M [2c]. Після обробки запиту клієнта він може викликати різні відповіді. Класичним прикладом є:
    • сторінка з повідомленням про помилку, якщо запит не вдалося обробити належним чином
    • сторінка підтвердження в іншому випадку

Обробник події повертає контролеру [Faces Servlet] результат у вигляді символьного рядка, який називається ключем навігації.

  1. навігація — контролер обирає сторінку JSF (= представлення), яку потрібно надіслати клієнту. Цей вибір здійснюється на основі ключа навігації, повернутого обробником подій.
  2. відповідь — обрана сторінка JSF надішле відповідь клієнту. Вона використовує свій шаблон M для ініціалізації своїх динамічних частин. Ця модель також може звертатися до шару [métier] [4a], щоб надати сторінці JSF необхідні дані.

У проєкті JSF:

  • контролером C є сервлет [javax.faces.webapp.FacesServlet]. Він знаходиться у бібліотеці [jsf-api.jar].
  • види V реалізовані сторінками JSF.
  • моделі M та обробники подій реалізовані класами Java, які часто називають «backing beans».
  • У версіях JSF та 1.x визначення бінів, а також правила переходу між сторінками задано у файлі [faces-config.xml]. У ньому міститься перелік переглядів та правила переходу від одного до іншого. Починаючи з версії JSF 2, визначення бінів можна здійснювати за допомогою анотацій, а переходи між сторінками — «жорстко» у коді бінів.

10.2. Робота додатка

При першому запуску додатка відображається така сторінка:

Потім заповнюємо форму та запитуємо інформацію про зарплату:

Отримуємо такий результат:

Ця версія розраховує уявну заробітну плату. Не слід звертати увагу на зміст сторінки, а лише на її оформлення. При натисканні кнопки [Raz] відбувається повернення на сторінку [A].

Помилки у введенні даних позначаються, як показано в наступному прикладі:

10.3. Проєкт NetBeans

Ми створимо першу версію додатка, у якій буде імітуватися рівень [métier]. Ми матимемо таку архітектуру:

Коли обробники подій або моделі запитуватимуть дані у шару [métier] [2b, 4a], цей шар надаватиме їм фіктивні дані. Мета полягає в тому, щоб отримати веб-шар, який коректно реагує на запити користувача. Коли це буде досягнуто, нам залишиться лише встановити серверний шар, розроблений у розділі 7.1:

Це буде версія 2 веб-версії нашого додатка PAM.

Проект NetBeans для версії 1 — це такий проект Maven:

  • у [1], конфігураційні файли
  • у [2], сторінки — у XHTML, а таблицю стилів
  • у [3], класи шару — у [web]
  • у [4] — об’єкти, що обмінюються між шаром [web] та шаром [métier], а також самим шаром [métier]
  • у [5], файл повідомлень для інтернаціоналізації додатка
  • у [6] — залежності додатка

Розглянемо деякі з цих елементів.

10.3.1. Конфігураційні файли

Файл [web.xml] — це файл, який за замовчуванням генерується NetBeans, а також містить налаштування сторінки винятків:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 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_3_0.xsd">
  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>  
  <context-param>
    <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
    <param-value>true</param-value>
  </context-param> 
  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</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>
  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>faces/index.xhtml</welcome-file>
  </welcome-file-list>
  <error-page>
    <error-code>500</error-code>
    <location>/faces/exception.xhtml</location>
  </error-page>
  <error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/faces/exception.xhtml</location>
  </error-page>
</web-app>
  • рядок 30: [index.html] — це головна сторінка додатка
  • рядки 32–39: налаштування сторінки винятків

Сторінка [exception.html] походить від [ref3]. Її код такий:

<?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:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
      <h:form id="formulaire">
        <h3><h:outputText value="#{msg['exception.header']}"/></h3>
        <h:panelGrid columnClasses="col1,col2" columns="2" border="1">
          <h:outputText value="#{msg['exception.httpCode']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.status_code']}"/>
          <h:outputText value="#{msg['exception.message']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.exception']}"/>
          <h:outputText value="#{msg['exception.requestUri']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.request_uri']}"/>
          <h:outputText value="#{msg['exception.servletName']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.servlet_name']}"/>
        </h:panelGrid>
      </h:form>
    </h:body>
  </f:view>
</html>

Будь-яке виключення, яке не обробляється явно кодом веб-додатка, призведе до відображення сторінки, подібної до наведеної нижче:

Файл [faces-config.xml] матиме такий вигляд:

<?xml version="1.0" encoding="UTF-8"?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
              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_2_0.xsd">

  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
</faces-config>

Зверніть увагу на наступні моменти:

  • рядки 9–14: файл [messages.properties] використовуватиметься для інтернаціоналізації сторінок. Доступ до нього на сторінках XHTML здійснюватиметься за допомогою ключа msg.
  • рядок 15: визначає файл [messages.properties] як такий, що має бути перевірений у першу чергу для повідомлень про помилки, які відображаються тегами <h:messages> та <h:message>. Це дозволяє перевизначити деякі повідомлення про помилки за замовчуванням з файлу JSF. Ця можливість тут не використовується.

10.3.2. Таблиця стилів

Файл [styles.css] має такий вигляд:


.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
}

Ось приклади коду JSF, у якому використовуються ці стилі:


<h:outputText value="#{msg['form.infos.employé']}"
 styleClass="titreInfos"/>

<h:panelGrid columns="3" 
rowClasses="libelle,info">

<h:message for="heuresTravaillées"
 styleClass="error"/>

10.3.3. Файл повідомлень

Файл повідомлень [messages_fr.properties] має такий вигляд:


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

Усі ці повідомлення використовуються на сторінці [index.xhtml], за винятком тих, що містяться у рядках 11–15, які використовуються на сторінці [exception.xhtml].

10.3.4. Область дії бінів

Біан [web.forms.Form] матиме область дії request:


import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class Form implements Serializable {

Біан [web.utils.ChangeLocale] матиме область дії «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{
  // локалізація сторінок
  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. Рівень [métier]

Рівень [métier] реалізує наступний інтерфейс IMetierLocal:


package metier;

import java.util.List;
import javax.ejb.Local;
import jpa.Employe;

@Local
public interface IMetierLocal {
  // отримати розрахунковий лист
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // список співробітників
  List<Employe> findAllEmployes();
}

Цей інтерфейс використовується в серверній частині клієнт-серверного додатка, описаного в розділі 7.1.

Клас Metier, який ми будемо використовувати для тестування шару [web], реалізує цей інтерфейс наступним чином:


package metier;

...
public class Metier implements IMetierLocal {
  
  // словник співробітників, індексований за номером SS
  private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
  // список співробітників 
  private List<Employe> listEmployes;
  
  // отримати платіжну відомість
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
    double nbHeuresTravaillées, int nbJoursTravaillés) {
    // отримати дані про працівника за номером SS
    Employe e=hashEmployes.get(SS);
    // повернути фіктивну платіжну відомість
    return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),new ElementsSalaire(100,100,100,100,100));
  }
  
  // список співробітників
  public List<Employe> findAllEmployes() {
    if(listEmployes==null){
      // створення списку з двох працівників
      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)));
      // словник співробітників, індексований за номером SS
      for(Employe e:listEmployes){
        hashEmployes.put(e.getSS(),e);
      }
    }
    // видається список співробітників
    return listEmployes;
  }
}

Розшифрувати цей код ми залишаємо на розсуд читача. Зверніть увагу на використаний підхід: щоб уникнути необхідності впроваджувати частину EJB додатка, ми імітуємо рівень [métier]. Коли рівень [web] буде визнано правильним, ми зможемо замінити його справжнім рівнем [métier].

10.4. Форма [index.xhtml] та її шаблон [Form.java]

Зараз ми створюємо сторінку XHTML форми, а також її шаблон.

Рекомендовані матеріали у [ref3]:

  • приклад № 3 (mv-jsf2-03) — перелік тегів, які можна використовувати у формі
  • приклад № 4 (mv-jsf2-04) — про випадаючі списки, що заповнюються з шаблону
  • приклад № 6 (mv-jsf2-06) — перевірка введених даних
  • приклад № 7 (mv-jsf2-07) — управління кнопкою [Raz]

10.4.1. крок 1


Завдання: Створити форму [index.xhtml] та її модель [Form.java], необхідні для отримання такої сторінки:


Компоненти для введення даних такі:

id
тип JSF
шаблон
роль
1
comboEmployes
<h:selectOneMenu>
String comboEmployesValue
List<Співробітник> getEmployes()
містить список співробітників у форматі
«ім’я прізвище».
2
heuresTravaillees
<h:inputText>
String heuresTravaillées
кількість відпрацьованих годин — фактичне значення
3
joursTravailles
<h:inputText>
Строка joursTravaillés
кількість відпрацьованих днів — ціле число
4
btnSalaire
<h:commandButton>
 
запускає розрахунок заробітної плати
5
btnRaz
<h:commandButton>
 
повертає форму до початкового стану
  • метод getEmployes поверне список співробітників, отриманий з рівня [métier]. Об’єкти, що відображаються у комбінованому списку, матимуть атрибут itemValue — номер співробітника SS, а також атрибут itemLabel — рядок, що складається з імені та прізвища співробітника.
  • Кнопки [Salaire] та [Raz] наразі не будуть підключені до обробників подій.
  • Буде перевірено правильність введених даних.

Image

Випробуйте цю версію. Зокрема, переконайтеся, що помилки введення даних правильно позначаються.

Примітка: важливо, щоб атрибути id компонентів сторінки не містили символів з діакритичними знаками. У Glassfish 3.1.2 це призводить до збою програми.

10.4.2. Крок 2


Завдання: заповніть форму [index.xhtml] та її шаблон [Form.java], щоб після натискання кнопки [Salaire] з’явилася така сторінка:


Кнопка [Salaire] буде пов’язана з обробником події calculerSalaire шаблону. Цей метод використовуватиме метод calculerFeuilleSalaire з шару [métier]. Ця платіжна відомість буде сформована для працівника, обраного в [1].

У шаблоні розрахунковий лист буде представлений таким приватним полем:


  private FeuilleSalaire feuilleSalaire;

з методами get та set.

Щоб отримати інформацію, що міститься в цьому об’єкті, на сторінці JSF можна написати такі вирази:


<h:outputText value="#{form.feuilleSalaire.employe.nom}"/>

Вираз атрибута value буде обчислено наступним чином:

[form].getFeuilleSalaire().getEmploye().getNom(), де [form] представляє екземпляр класу [Form.java]. Читач може переконатися, що методи get, які тут використовуються, дійсно існують відповідно у класах [Form], [FeuilleSalaire] та [Employe]. Якби це не було так, під час обчислення виразу було б згенеровано виняток.

Перевірте цю нову версію.

10.4.3. Крок 3


Завдання: заповніть форму [index.xhtml] та її шаблон [Form.java], щоб отримати таку додаткову інформацію:


Ми будемо дотримуватися того самого підходу, що й раніше. Виникає складність із символом валюти євро, який міститься, наприклад, у [1]. У рамках інтернаціоналізованого додатка було б краще мати формат відображення та валютний знак, що використовуються в locale (en, de, fr, ...). Це можна отримати таким чином:


          <h:outputFormat value="{0,number,currency}">
            <f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
</h:outputFormat>

Можна було б написати:


          <h:outputText value="#{form.feuilleSalaire.employe.indemnite.entretienJour} є">

але з локаллю en_GB (англійська GB) відображенням продовжуватиме бути євро, тоді як слід було б використовувати фунт стерлінгів (£). Тег <h:outputFormat> дозволяє відображати інформацію відповідно до локалі locale сторінки JSF, що відображається:

  • рядок 1: відображає параметр {0}, який є числом (number), що позначає грошову суму (currency)
  • рядок 2: тег <f:param> присвоює значення параметру {0}. Другий тег <f:param> присвоїв би значення параметру {1} і так далі.

10.4.4. крок 4

Рекомендовані матеріали: приклад № 7 (mv-jsf2-07) у [ref3].


Завдання: заповніть форму [index.xhtml] та її шаблон [Form.java] для управління кнопкою [Raz].


Кнопка [Raz] повертає форму до того стану, в якому вона перебувала під час першого запиту за допомогою GET. Тут є кілька складнощів. Деякі з них було пояснено в [ref3].

Форма, що відображається після натискання кнопки [Raz], не є повною формою, а лише її частиною saisie:

Image

Цей результат можна отримати за допомогою тегу <f:subview>, використаного таким чином:


      <f:subview id="viewInfos" rendered="#{form.viewInfosIsRendered}">
... la partie du formulaire qu'on veut pouvoir ne pas afficher
</f:subview>

Тег <f:subview> охоплює всю ту частину форми, яку можна відображати або приховувати. Будь-який компонент можна відображати або приховувати за допомогою атрибута rendered. Якщо rendered="true", компонент відображається; якщо rendered="false", — ні. Якщо атрибут rendered отримує значення в шаблоні, відображення компонента можна контролювати програмно.

У наведеному вище прикладі відображення подання viewInfos буде керуватися за допомогою такого поля:


  private boolean viewInfosIsRendered;

разом із відповідними методами get та set. Методи, що обробляють натискання на кнопки [Salaire] та [Raz], оновлюватимуть це булеве значення залежно від того, чи слід відображати вигляд viewInfos.