TP 0 - Introduction à Jakarta EE

Retour TPs

Introduction

Ce TP permet une prise de contact directe avec les technologies Maven, Git, GitHub et les 2 spécifications Jakarta EE, CDI et JSF.

  • Maven est un outil de gestion de projet qui simplifie le processus de développement en automatisant la gestion des dépendances, la construction et le déploiement des projets Java.
  • Git est un système de contrôle de version qui permet aux développeurs de suivre et gérer les modifications apportées au code source au fil du temps.
  • GitHub permet de travailler en équipe sur un même projet et de sauvegarder vos projets sur le cloud.
  • CDI (Context and Dependency Injection) permet de connecter différentes parties d'une application en injectant (c'est-à-dire en fournissant) les dépendances nécessaires aux composants.
  • JSF (Jakarta Faces) permet de développer des interfaces utilisateur Web interactives, en utilisant des composants.

Les autres TPs vont utiliser Jakarta EE. Si vos connaissances sur Jakarta EE sont réduites, ce TP est une bonne occasion de préparer votre travail avec les autres TPs.

Il est conseillé de terminer ce TP avant le début des cours. Toutes les corrections de ce TP sont ouvertes et très détaillées. Prenez le temps de bien lire les explications.

Présentation de Jakarta EE

Très rapide présentation, avec parfois quelques raccourcis (je n'entre pas dans tous les détails ni dans les options offertes par Jakarta EE).

Un peu d'histoire

  1. Java EE standard pour le développement d’applications d’entreprise avec Java, conduit par Oracle, propriétaire du langage Java.
  2. En 2017 Oracle transmet Java EE à la Fondation Eclipse.
  3. Jakarta EE, nouvelle gouvernance, nouveau nom pour Java EE

Généralités

  • Jakarta EE est open source. Site Web : https://jakarta.ee/
  • Membres stratégiques : IBM, Oracle, Payara, Fujitsu, Tomitribe (Tomcat).
  • Membres entreprise : Microsoft, Red Hat, 2 entreprises chinoises.
  • Membres participants : VMware (Spring), Webtide (Jetty),...
  • Membres invités : Apache, groupes d’utilisateurs de Java.
  • Une quarantaine de spécifications : CDI, JSF, JPA, JAX-RS (REST), JSON-P, JSON-B,...
  • Une application peut être distribuée dans un fichier jar. Une application Web simple est distribuée dans un fichier war qui est un fichier jar avec une structure particulière.
  • Même s'il existe d'autres façons de faire, le plus souvent l'exécution d'une application Jakarta EE nécessite un serveur d'application ; c'est un logiciel qui fournit à l'application Jakarta EE l'environnement et les librairies dont elle a besoin (c'est pour cela que les fichiers jar de ces applications sont souvent de tailles réduites).

Configuration d'une application

Jakarta EE suit la fomule "convention plutôt que configuration" : Pas besoin de configurer les pratiques les plus courantes ; seules les pratiques inhabituelles doivent être configurées. Les configurations sont faites par annotations ou fichiers XML.

CDI

Spécification centrale de Jakarta EE.

Quand le code d'une classe a besoin d'une instance d'une autre classe, elle peut l'injecter avec l'annotation @Inject.

Une instance peut même être injectée dans une page JSF.

JSF

Spécification pour les interfaces utilisateur Web.

Solution simple et rapide pour écrire des interfaces utilisateur Web.

JSF est "server-side" : une grande partie des traitements pour l’interface Web se fait sur le serveur Web. Donc ne peut pas supporter des milliers d'utilisateurs simultanés (convient pour quelques centaines d'utilisateurs simultanés).

Une page JSF représente une page HTML qui sera vue par l'utilisateur. Elle contient du code HTML ou du code qui ressemble à HTML, avec des parties spéciales entourées de #{ } (on les appelle des expressions EL) qui représentent

  • Des valeurs de propriétés de classes Java. Une propriété Java est représentée par un setter et/ou un getter. Par exemple la propriété "nom" est représentée par les méthodes setNom et/ou getNom. Pour les propriétés booléennes, le getter peut s'appeler is<nom propriété> au lieu de get<nom propriété>. Une variable d'instance est souvent associé à une propriété, mais pas nécessairement ; par exemple une propriété "etudiants" peut être définie par getEtudiants() qui retourne une liste d'étudiants, créée dans la méthode getEtudiants(), sans mettre la liste dans une variable d'instance.
  • Ou des méthodes de classes Java qui seront exécutées sur le serveur HTTP. Par exemple, une méthode qui sera exécutée sur le serveur lorsqu'un formulaire HTML sera soumis.

Les classes Java dont les propriétés ou les méthodes sont utilisées dans une page JSF sont appelées des backing beans. Ce sont des classes qui aident à effectuer des traitements associés à la page JSF (to back = soutenir, renforcer).

Exemple basique de pages JSF

Les pages JSF sont des fichiers d'extension ".xhtml" (format XML). Ces pages sont sur le serveur. Quand l'utilisateur tape l'URL d'une telle page dans son navigateur, la page est transformée sur le serveur en une page HTML et envoyée au client HTTP en réponse à la requête HTTP GET.

La page JSF presentation.xhtml suivante permet à l'utilisateur d'entrer son nom dans une zone de saisie de texte :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="jakarta.faces.html">
  <h:head>
    <title>Présentation</title>
  </h:head>
  <h:body>
    <h3>Présentation</h3>
    <h:form>
      Nom : <h:inputText value="#{utilisateur.nom}"/>
      <h:commandButton value="Enregistrer" action="#{utilisateur.direHello()}"/>
    </h:form>
  </h:body>
</html>

Explications :

  • L'espace de noms XML d'alias "h" correspond à un ensemble de composants JSF. De nombreux composants correspondent aux balises HTML mais un composant peut aussi ne pas correspondre à une balise HTML.
  • Le formulaire HTML contiendra une zone de saisie de texte et un bouton pour soumettre le formulaire.
  • Le nom tapé par l'utilisateur sera rangé dans la propriété "nom" du backing bean nommé "utilisateur". JSF travaille avec des propriétés ; une simple variable d'instance ne suffirait pas.
  • Le composant <h:commandButton> est un bouton de soumission de formulaire. La soumission du formulaire enverra une requête HTTP POST au serveur.
  • La méthode direHello() est une méthode dite "action" du backing bean. Elle sera appelée à la fin du traitement de la soumission du formulaire par le serveur. Cette méthode pourrait exécuter des actions complexes. Sa valeur de retour (de type String) sera le nom de la page JSF qui sera retournée en réponse à la requête POST.

Backing bean :

@Named
@RequestScoped
public class Utilisateur {
  private String nom;
  public String getNom() {
    return nom; 
  }
  public void setNom(String nom) { 
    this.nom = nom;
  }
  public String direHello() {
    return "hello";
  }
  ...

Explications :

  • @Named est une annotation qui indique que la classe est un backing bean : une instance de la classe peut être injectée/utilisée dans une page JSF. Par défaut, le nom d'un backing bean est le nom de sa classe avec la majuscule du début transformée en minuscule (utilisateur).
  • @RequestScoped indique que la portée d'une instance injectée de la classe est "requête" : CDI la supprimera automatiquement à la fin de la requête HTTP. Choisir la bonne portée des classes gérées par CDI est important. Autres portées CDI :
    • "view" : l'instance injectée est supprimée quand l'utilisateur change de page.
    • "session" : elle est supprimée à la fin de la session de travail de l'utilisateur.
    • "application" : elle est supprimée seulement quand l'application est supprimée du serveur d'application.
  • On reconnait la propriété "nom" utilisée dans la page JSF. En Java, une propriété correspond à un getter et/ou un setter ; il pourrait ne pas y avoir de variable d'instance associée à la propriété.
  • On trouve aussi la méthode direHello() qui est utilisée par la page JSF. Comme cette méthode est associée à l'attribut action du composant JSF commandButton, elle sera exécutée quand l'utilisateur soumettra le formulaire (une requête HTTP POST sera envoyée au serveur). Le type d'une telle méthode doit être String. La valeur retournée est le nom de la page JSF qui sera affichée en réponse à la requête POST. Si la valeur est null, l'utilisateur restera sur la même page.
  • Enchainement des actions :
    1. L'utilisateur tape l'URL qui correspond à la page JSF dans son navigateur.
    2. Dans la page JSF, les expressions EL sont interprétées pour transformer la page JSF en page HTML. On suppose qu'aucune instance de la classe Utilisateur n'existe et donc CDI en crée une. La propriété nom n'a pas encore de valeur donc rien ne sera affiché dans la zone de saisie. L'expression EL pour le bouton de soumission du formulaire est remplacée par du code qui sera exécuté quand le formulaire sera soumis.
    3. La page HTML est affichée dans le navigateur.
    4. L'utilisateur tape son nom (supposons qu'il a tapé "Bob") et soumet le formulaire.
    5. Une requête POST est envoyée au serveur HTTP.
    6. La page JSF est restaurée avec le nom tapé par l'utilisateur : quand la requête arrive sur le serveur une nouvelle instance de la classe Utilisateur est créée et le nom tapé par l'utilisateur est affectée à sa propriété "nom" (par la méthode setNom).
    7. La méthode direHello() est exécutée (c'est la méthode liée à la soumission du formulaire). Dans ce cas simpliste, la méthode ne fait rien mais retourne la valeur "hello". JSF interprète cette valeur de retour comme le nom de la prochaine page JSF à transmettre au client HTTP en réponse à la requête POST.
    8. La page hello.xhtml (voir le code ci-dessous) est transformée en page HTML. Elle comporte une expression EL qui utilise la propriété nom de utilisateur. CDI regarde s'il existe déjà une instance de la classe utilisateur. Oui, l'instance créée à l'étape 2 existe encore car elle ne sera détruite qu'à la fin du traitement de la requête POST par le serveur (c'est-à-dire quand la réponse à la requête POST sera envoyée en réponse au client HTTP) puisque . La propriété de cette instance est "Bob". Cette page HTML s'affiche dans le navigateur de l'utilisateur avec le message "Hello Bob".

Voici la page hello.xhtml :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="jakarta.faces.html">
  <h:head>
    <title>Hello</title>
  </h:head>
  <h:body>
    Hello #{utilisateur.nom}
  </h:body>
</html>

Services rendus par JSF

JSF permet d'écrire rapidement une application Web. Voici les principaux services rendus par JSF :

  • Conversion entre les textes saisis par l’utilisateur et les valeurs Java du serveur.
  • Validation des données saisies par l’utilisateur facilitée. Réaffichage automatique du formulaire avec les messages d'erreur si les données ne sont pas toutes valides.
  • Automatisation de l’affichage des messages d’erreur si problèmes de conversion ou de validation.
  • Internationalisation de l'interface utilisateur simplifiée.
  • Support d’Ajax sans programmation. Avec Ajax, les applications Web peuvent envoyer et recevoir des données en arrière-plan afin que seules de petites parties de la page soient actualisées selon les besoins
  • Templates graphiques, pour que les pages d'une application Web respectent une charte graphique.
  • Librairies de composants, en particulier la librairie PrimeFaces dont vous trouverez un showcase à l'URL https://www.primefaces.org/showcase/index.xhtml. Vous y trouverez par exemple un composant pour des tables de données (datatable) en https://www.primefaces.org/showcase/ui/data/datatable/crud.xhtml, des composants pour les graphiques, pour le multimedia, pour les cartes Google (Gmap) et de très nombreux autres composants de tous types.

Création du projet

Dans ce TP vous allez développer une application de type "Web", qui correspond au profile Web de Jakarta EE. Un profile est un sous-ensemble de la plateforme Jakarta EE complète.

Vous allez créer un projet Maven. Maven est le logiciel qui va vous permettre de créer le projet, de le construire (construire en particulier le fichier jar pour l'exécution du projet), de gérer les dépendances du projet (par exemple, le projet dépendra de l'API Jakarta EE). Les fichiers jar des dépendances utilisées par le projet seront récupérés automatiquement par Maven sur l'entrepôt (repository) central de Maven et enregistrés dans l'entrepôt local de Maven qui est sur votre ordinateur. Sous NetBeans vous pourrez voir la liste de ces fichiers dans l'onglet "Services", entrée "Maven Repositories".

Remarque : si vous modifiez le fichier pom.xml, il faut sans doute faire un "reload Maven" pour que les dépendances soient chargées dans le projet. Avec IntelliJ : clic droit sur pom.xml > Maven > Reload project (si vous modifiez pom.xml, une petite icôneMaven reload située en haut à droite de la fenêtre d'édition offre un raccourci pour cela). Si ça ne suffit pas, cochez "Redeploy" au moment d'exécuter du projet (menu Run). NetBeans : un clean and build suffit.

Si toutes les dépendances utilisées par votre projet ne sont pas déjà dans l'entrepôt local, vous devez être connecté à Internet pour créer le projet.

Avec NetBeans

Avec IntelliJ

Examen du projet généré par l'IDE

Le projet créé contient des fichiers de configuration :

  • web.xml dans le répertoire WEB-INF. Pour le Web comme son nom l'indique ; ne contient presque rien si la configuration suit les pratiques habituelles. Si ce fichier n'a pas été généré, vous le créerez dans la suite de ce TP.
  • beans.xml au même endroit que web.xml. Pour CDI, presque vide lui aussi. L'attribut bean-discovery-mode="all" de la balise beans indique que toutes les classes pourront être considérées comme des beans CDI (pourront être injectées et pourront injecter des beans). Par défaut, la valeur de cet attribut est "annotated", ce qui signifie que seules les classes qui ont une annotation CDI (par exemple une portée CDI) seront considérées comme des beans CDI.
  • persistence.xml dans le répertoire META-INF. Pour l'utilisation des bases de données avec JPA. Presque vide.
  • pom.xml dans l'entrée "Project Files". Utilisé par Maven. Vous y retrouvez les informations que vous avez données lors de la création du projet.

Git et GitHub pendant les TPs

Git est inclus dans NetBeans et IntelliJ.

Cette page décrit comment utiliser Git pour gérer les versions de votre code, et GitHub pour sauvegarder ces versions à distance. Vous devez appliquer ce qui y est dit avant d'aller plus loin.

Notez bien : Si vous voulez profiter des bonus accordés pour votre travail dans les TPs, vous devez effectuer tous les commits, à chacune des étapes importantes des TPs. Un projet GitHub sans ces commits ne pourra pas vous rapporter ces bonus. Les noms de vos packages doivent aussi vous identifier (il doit comporter votre nom ; par exemple ma.xxxxx.entity où xxxxx est votre nom, éventuellement lègèrement modifié).

Vous devez donc créer un repository GitHub pour ce projet. A chaque étape d'avancement du projet vous devrez tester et, si tout va bien, faire un commit et ensuite un push du projet sur GitHub. Commencez tout de suite par un commit (message : "Etat initial de l'application" ou un message similaire) et un push.

Quand le TP sera fini, si vous voulez un bonus, envoyez-moi un email avec le bon format pour le sujet, "[EMSI-IA] TP 0 terminé - nn" (nn est votre numéro dans la liste des étudiants). Dans le message donnez-moi l'URL du repository GitHub.

Description de l'application Web

Vous aller écrire une application Web très simple qui vous présentera les grandes lignes de Jakarta EE, du moins de JSF et CDI.

Voici comment va se dérouler l'exécution de cette application :

  1. L'utilisateur va saisir un texte dans une page HTML qui sera envoyé au serveur.
  2. Le serveur va faire un traitement en utilisant la question.
  3. Le résultat du traitement sera affiché dans une zone "Réponse" de la page de départ. L'historique de la conversation sera affiché dans une autre fenêtre à droite.

Une interface utilisateur semblable sera utilisée dans les TP à venir pour faire un chat avec un LLM. Pour le moment ce traitement ne fait pas grand-chose : il change la casse (majuscules/minuscules) de la question et ajoute l'intitulé du "rôle de l'API" (ça sera un rôle qu'on donnera plus tard au LLM) et entoure le tout avec des "||".

Une liste déroulante va permettre de choisir "le rôle de l'API".

Voici l'aspect de l'interface utilisateur :

Aspect de l'interface utilisateur

Voici à quoi va ressembler l'interface utilisateur de votre application après des échanges avec le serveur (affichés à droite) :

Interface utilisateur avec conversation

Code de l'application Web

Page JSF

Elle contient essentiellement des <h:textarea> pour afficher la question de l'utilisateur, la réponse de l'API et l'historique de la conversation depuis le début entre l'utilisateur et l'API.

Une liste déroulante permet de choisir le "rôle de l'API" (dans les autres TPs, l'API sera l'API d'un LLM ; pour ce TP, ce rôle ne signifie rien) : "helpful assistant", "traducteur français-anglais" ou "guide touristique". Le choix ne peut être effectué qu'une seule fois par session. Pour changer de rôle, l'utilisateur doit cliquer sur le bouton "Nouveau chat", ce qui démarre un nouveau chat.

Un bouton permet d'envoyer les questions de l'utilisateur et d'effacser la dernière question et la dernière réponse pour nettoyer les zones avant une nouvelle question. Des boutons permettent de copier rapidement le contenu de chaque textarea en cas de besoin.

Créez la page JSF : clic droit sur l'entrée "webapp" de l'application et choisir New > JSF/Facelets. Nom de la page : index.xhtml. Si l'option Jsf/Facelets n'apparait pas, installez le plugin IntelliJ "Jakarta EE: Server Faces".

Si vous ne connaissez pas JSF, remplacez le code de cette page par ce code et lisez le code pour essayer de le comprendre.

Dans le head de la page, on voit que la page utilise un fichier CSS et un fichier JavaScript. Vous devez les placer au bon endroit pour JSF. Les fichiers de ressources CSS, JavaScript, images,... doivent être dans le répertoire resources (un seul "s") directement sous webapp. On peut le voir dans le head de la page leur emplacement exact sous ce répertoire resources.

Vous remarquerez comment il est possible d'emboîter des <h:panelGrid>, des <h:panelGroup> et d'utiliser CSS pour créer une interface utilisateur complexe.

La page JSF utilise la bibliothèque de composant PrimeFaces dont j'ai parlé au début de ce TP. Il va donc falloir ajouter une dépendance vers cette librairie dans pom.xml (utilisez la version indiquée ou une version plus récente de PrimeFaces) :

<dependency>
  <groupId>org.primefaces</groupId>
  <artifactId>primefaces</artifactId>
  <version>14.0.2</version>
  <classifier>jakarta</classifier>
</dependency>

N'oubliez pas le commit Git et GitHub... Choisissez bien le message du commit. Il vous servira à revenir à ce commit en cas de problème.

Backing bean

La portée du backing bean sera "view" afin de garder les informations sur la conversation (c'est un chat...) entre l'utilisateur et l'API. Dans la conversation, il y aura plusieurs requêtes POST envoyées au serveur (une à chaque "question" envoyée au serveur). Si une portée "requête" avait été choisie, il aurait été difficile de faire afficher la conversation.

Remarque :

  • Si la méthode "action" associée au bouton de soumission du formulaire retourne null, l'utilisateur reste sur la même page et il est toujours dans la même vue (view) (donc la même instance du backing bean sera utilisé).
  • Si la méthode "action" associée au bouton de soumission du formulaire retourne "index.xhtml" (la page en cours), l'utilisateur reste sur la même page mais JSF considère qu'il a changé de vue et donc une autre instance du backing bean sera utilisée. Voir code associé au bouton "Nouveau chat".

Pour créer le backing bean, créez une classe Java (Menu File > New > Java Class). Utilisez ce code que vous placez où il faut (n'oubliez pas de modifier le nom du package de la classe ; le sous-package final s'appelle "jsf"). Vous devez lire le code et essayer de le comprendre.

Git et GitHub...

Modification de web.xml

Si vous lancez tout de suite l'application, il est souhaitable que la page index.xhtml soit affichée.

Pour cela modifiez le contenu de web.xml placé sous WEB-INF :

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">
    <welcome-file-list>
        <welcome-file>index.xhtml</welcome-file>
    </welcome-file-list>
</web-app>

Test de l'application

Pour l'IDE NetBeans

Clic droit sur l'application et Run.

Pour l'IDE IntelliJ IDEA

Changez le nom de la configuration d'exécution pour qu'il convienne mieux ; par exemple "tp 0", plutôt que "Payara 6.xxxx" : Menu Run > Edit configurations....

Normalement cette configuration d'exécution

  • Indique que le serveur d'application est Payara.
  • A une section "Before launch" qui contient un build de l'application et ensuite la création du fichier war de distribution de l'application (exploded signifie qu'il est décompacté).
  • Si vous cliquez sur l'onglet Deployment, la section "Deploy at the server startup" indique que la war explosé sera déployé.

Testez l'application : menu Run et choisissez la bonne configuration d'exécution.

Pendant l'exécution

Vérifiez que la page "index.xhtml" affichée par le navigateur est bien composée de code HTML, et pas du code de la page JSF, comme il est expliqué dans les étapes 2 et 8 de l'enchainement des actions. Si vous utilisez Chrome, tapez Ctrl-U dans la page pour voir le source de la page.

Testez l'envoi de la question sans avoir écrit la question. Un message d'erreur doit s'afficher (voir code du backing bean).

Git et GitHub si tout va bien.

Modification pour que les accents s'affichent correctement

Vous avez peut-être remarqué que les accents ne s'affichent pas correctement. Le codage des caractères en UTF-8 est considéré comme un codage ISO-8859. Le plus simple pour corriger ce problème est d'ajouter un filtre qui intervient lors de l'envoi des requêtes vers le serveur et lors du retour des réponses. Ajoutez ce filtre qui intervient pour l'envoi des requêtes (j'ai laissé en commentaire le code pour l'intervention sur les réponses).

Vous devez ajouter aussi ceci dans web.xml pour déclarer le filtre.

Testez.

Git et GitHub.

Correction

Page JSF (n'oubliez pas les fichiers JavaScript et CSS)
Backing bean
Filtre
Déclaration filtre

Exercice pour le bonus

Si vous voulez gagner un bonus avec ce TP, il vous reste juste une étape. Une petite révision de Java : à la place du traitement effectué sur le serveur pour générer la réponse, écrivez un autre traitement. Faites simple, mais votre traitement doit être personnel, vous ne devez pas reprendre l'idée d'un autre étudiant.

Quand vous avez testé votre code, commit et push. Vérifiez que le push a bien poussé tout votre code dans votre repository GitHub (en allant sur GitHub) et envoyez-moi un email avec l'URL de votre repository GitHub, comme expliqué par ailleurs. Dans l'email expliquez-moi le traitement que vous avez choisi d'implémenter.

L'attribution du bonus dépend de votre premier envoi. Vous pourrez ensuite corriger des erreurs en tenant compte de mes remarques mais vous perdrez alors une partie du bonus, ou tout le bonus.

Retour TPs