Ce TP permet une prise de contact directe avec les technologies Maven, Git, GitHub et les 2 spécifications Jakarta EE, CDI et JSF.
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.
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).
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.
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.
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
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).
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 :
<h:commandButton>
est un bouton de soumission de formulaire. La soumission du formulaire enverra une requête HTTP POST au serveur.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 :
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.setNom
).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.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>
JSF permet d'écrire rapidement une application Web. Voici les principaux services rendus par JSF :
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ône 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.
Le projet créé contient des fichiers de configuration :
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.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.
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 :
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 :
Voici à quoi va ressembler l'interface utilisateur de votre application après des échanges avec le serveur (affichés à droite) :
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.
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 :
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...
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>
Clic droit sur l'application et Run.
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
Testez l'application : menu Run et choisissez la bonne configuration d'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.
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.
Page JSF (n'oubliez pas les fichiers JavaScript et CSS)
Backing bean
Filtre
Déclaration filtre
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.