Mise en place d’une application application JSF, Spring et Hibernate
Last update: 06/ 08/2010 Version: 1.1 Status: In progress - Being checked - Validated
WRITTEN BY
Name : Yosr Chaâri Department : TS/SE Date : 06/08/2010 Stamp :
1- Introduction générale Le but de ce tutorial est de s’initier au développement d’applications MVC basées sur JSF, Spring et Hibernate.
2- Base de données Notre base de données consiste en deux tables : contacts et contrats. Les commandes SQL pour la création de la base et des tables sont les suivantes : --- Structure de la table `contacts` -CREATE TABLE IF NOT EXISTS `contacts` ( `CIN` varchar(20) NOT NULL, `firstName` varchar(15) NOT NULL, `lastName` varchar(15) NOT NULL, `email` varchar(20) NOT NULL, PRIMARY KEY (`CIN`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; --- Contenu de la table `contacts` -INSERT INTO `contacts` (`CIN`, `firstName`, `lastName`, `email`) VALUES ('08765645', 'Ali', 'Ben Salah', '[email protected]'), ('05456783', 'Mohamed', 'Ben Abdallah', '[email protected]'); -- ---------------------------------------------------------- Structure de la table `contrats` -CREATE TABLE IF NOT EXISTS `contrats` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ref` varchar(11) NOT NULL, `idclient` varchar(20) NOT NULL, `datec` date NOT NULL, `duree` int(11) NOT NULL, PRIMARY KEY (`id`), FOREIGN KEY (idclient) references contacts(CIN) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ; --- Contenu de la table `contrats` -INSERT INTO `contrats` (`id`, `ref`, `idclient`, `datec`, `duree`) VALUES (1, '33', '08765645', '2010-05-18', 7), (2, '2', '08765645', '2010-05-18', 9);
3- Création d’un nouveau projet La première chose à faire est de créer un projet web dynamique. Pour cela, accéder au menu « File -> New -> Other » et choisir « Dynamic Web P roject » sous l’option « Web ».
Figure 1 : Cr éation d'un projet web dynamique
Nommer le projet « GestionContrats » et configurer par la suite le serveur d’application. Pour cela, cliquer sur le bouton « New ». Choisir de la liste des serveurs celui que nous allons utiliser (Apache Tomcat v6.0 dans notre cas) et cliquez sur le bouton « Next ». Une fenêtre apparaît, (il est nécessaire d’avoir installé le serveur) :
Figure 2: Configuration du serveur
Cliquer sur « Finish », le reste des éléments de la fenêtre de création du projet sont remplies. Cliquer sur « Finish ». Une fois le projet créé, il faudra ajouter les jars nécessaires sous le dossier lib :
Figure 3: Librairies du projet
4- Installation d’Hibernate tools Hibernate tools est un plugin qui permet de générer automatiquement les classes du mapping à partir de la base de données. Pour l’installer, il suffit d’accéder à l’option « Software Updates » du menu
« Help » ;
onglet
« Available
Software »
et
ajouter
le
site
http://download.jboss.org/jbosstools/updates/stable/ en cliquant sur le bouton « Add Site ». Redémarrer Eclipse. Il faut maintenant configurer la connexion à la base de données. Créer un nouveau répertoire de type « source folder » nommé config. Cliquer dessus avec le bouton droit et choisir « New -> Other ».
Figure 4: Création du fichier de configuration Hibernate
Cliquer sur « Next » et remplir la fenêtre avec les informations locales :
Figure 5: Configuration pour la création du fichier de config.
Par la même occasion, configurer la console « Hibernate tools » pour disposer d’un outil de génération de mapping. Pour configurer les paramètres de connexion à la base, cliquez sur « New » :
Figure 6: Configuration des paramètres de la base de données
Il ne faut pas oublier de sélectionner le jar correspondant à notre driver. Cliquer sur « Finish ». Le fichier de configuration « hibernate.cfg.xml » a été créé comme suit : com.mysql.jdbc.Driverjdbc:mysql://localhost:3306/commercialerootorg.hibernate.dialect.MySQLDialect
Ouvrir la vue « Hibernate Configuration » et vérifier la conformité de l’affichage à la base. On est prêt à lancer la génération de code. Pour cela, il suffit de choisir l’option « Hibernate Code Generation Configurations » dans l’icône de « Hibernate Tools ».
Figure 7: Lancement de la génération de code
Il faudra par la suite préciser les détails de la génération dans la fenêtre affichée :
Figure 8: Précision des détails de la génération
Et dans l’onglet « Exporters » :
Figure 9: Onglet Exporters
Cliquer sur « Run » ; nous pouvons voir les classes (entités) générées dans le « package explorer ». Comme vous pouvez le remarquer, les entités sont générées avec l’ensemble des annotations nécessaires au mapping « Hibernate ». Par contre, nous remarquons que les annotations relatives à la clé étrangère ne sont pas générées. On modifie donc la classe « Contrats.java » comme suit : package com.oxia.formation; import static javax.persistence.GenerationType.IDENTITY ; import java.util.Date; import import import import import import import import import import
@Temporal(TemporalType.DATE ) @Column(name = "datec", nullable = false, length = 10) public Date getDatec() { return this.datec; } public void setDatec(Date datec) { this.datec = datec; }
@Column(name = "duree", nullable = false) public int getDuree() { return this.duree; } public void setDuree(int duree) { this.duree = duree; }
}
5- Couche d’accès aux données (DAO) Pour chaque entité, créer l’interface dans le package DAO (ContactsDao et ContratsDao).
Créer ensuite les implémentations des interfaces dans le package « ipml » (ContactsDaoImpl et ContratsDaoImpl). Normalement, pour bénéficier des fonctionnalités et des API Spring, chaque implémentation devrait étendre la classe « HibernateDAOSupport ». Mais pour des raisons de bonnes pratiques, nous allons faire autrement. En effet, les opérations de base pour la persistance des données sont toutes identiques et ne diffèrent que par le type d’objet à manipuler (création, consultation, mise à jour et suppression). Ces méthodes vont alors être répétées et redéfinies dans chaque DAO. Cependant, avec les types génériques,
T findById(ID id); T findById(ID id, Class extends T> clazz); List findAll(); void update(T entity); void delete(T entity);
}
Créer par la suite l’implémentation de cette interface « UtilD aoImpl » de laquelle vont hériter toutes les DAOs de nos entités : package com.oxia.formation.dao.impl; import java.io.Serializable; import java.util.List; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import com.oxia.formation.dao.UtilDao; public class UtilDaoImpl extends HibernateDaoSupport implements UtilDao { final transient protected Log log = LogFactory.getLog (this.getClass()); private Class clazz; public UtilDaoImpl(Class clazz) { this.clazz = clazz; } public void create(T entity) { getHibernateTemplate().save(entity); if(log.isDebugEnabled()){
Implémentation package com.oxia.formation.dao.impl; import com.oxia.formation.Contrats; import com.oxia.formation.dao.ContratsDao; public class ContratsDaoImpl extends UtilDaoImpl implements ContratsDao { public ContratsDaoImpl() { super(Contrats.class); } public List findContratsByContact(String idclient) { return getHibernateTemplate().findByNamedParam( "FROM Contrats c WHERE c.client.CIN = :idclient", "idclient",idclient); } }
Interface spécifique pour l’entité « Contacts » package com.oxia.formation.dao; import java.util.List; import com.oxia.formation.Contacts; public interface ContactsDao extends UtilDao { List getContacts(); }
Implémentation package com.oxia.formation.dao.impl; import java.util.List; import com.oxia.formation.Contacts; import com.oxia.formation.dao.ContactsDao; public class ContactsDaoImpl extends UtilDaoImpl implements ContactsDao { public ContactsDaoImpl() { super(Contacts.class); }
@SuppressWarnings("unchecked") public List getContacts() { return getHibernateTemplate().loadAll(Contacts.class); } }
De cette manière, le DAO pourra appeler les méthodes du DAO générique ainsi que celles qui lui sont propres.
6- Fichiers de configuration Spring Nous passons maintenant à la création des fichiers de configuration de Spring : Nous allons commencer par un fichier « ApplicationContext-ressources.xml » qui contient les paramètres de la connexion à la base de données :
Ainsi, le fichier « hibernate.cfg.xml » peut être éliminé puisque toutes les informations qu’il contient sont présentes dans ce fichier.
Ensuite, nous créons le fichier « ApplicationContext.xml » sous le dossier « WEB-INF ». Ce fichier contient tous les beans gérés par Spring. On va commencer par les paramètres de générateur de session « session factory ». Cette configuration consiste à spécifier les paramètres d’Hibernate, les classes annotées ainsi que la classe responsable de la gestion de la session.
Le fichier suivant est « ApplicationContext-Dao.xml » ; il permet de spécifier les Daos de l’application.
Nous créons par la suite un autre fichier xml (« ApplicationContext-transaction.xml ») pour la gestion des transactions.
Une fois ces fichiers créés, il faut dire à notre application de lancer le framework Spring en utilisant cette configuration. Pour cela, il faudrait les ajouter dans le fichier « web.xml ». contextConfigLocation/WEB-INF/ApplicationContext*.xml
7- Couche présentation Nous allons maintenant passer à la couche présentation. Le but est d’avoir un menu « Contact » qui nous permet de consulter la liste de contacts et d’éditer un contact donné. Nous commençons par la page « template.xhtml » qui définit la structure des pages de l’application. Insert title here
Cette page fait appel à la page « header.xhtml » qui définit l’entête des pages de l’application, qui consiste au menu dans notre application.
Nous allons maintenant mettre en place les pages « RechercheContact.xhtml », « Li stContact.xhtml» et « onecontact.xhtml». ListContact.xhtml
contacts.xhtml RichFaces
onecontact.xhtml
Cette page permet la création d’un nouveau contact ou la mise à jour d’un contact existant (accès à partir du lien edit de la page liste). Tous les champs sont obligatoires ; un message d’erreur est affiché si tel n’est pas le cas.
Figure 10: Erreur si les champs ne sont pas renseignés
Aussi, un message d’erreur est affiché si l’utilisateur essaye d’introduire un contact déjà existant (CIN existante).
Figure 11: Tentative d'insertion d'un id existant
RechercheContact.xhtml
Cette page nous mène à la page qui affiche les détails du contact recherché (CIN, nom et prénom, mail et les détails de ses contrats). DetailContact.xhtml
xmlns:a4j="http://richfaces.org/a4j">
Figure 12: Détail d'un contact donné
Nous passons maintenant au contrôleur « ContactCtr.java » package com.oxia.formation.ctr; import java.io.Serializable; import java.util.List; import import import import import
public class ContactCtr implements Serializable { private static final long serialVersionUID = 1L; private ContratsDao contratsDao; private ContactsDao contactsDao; private DataModel features; private Contacts selectedContact = new Contacts(); private Contacts contact; private List contacts; private List listeContrats; private boolean creation;
@PostConstruct public void init() { contacts = contactsDao.getContacts(); features = new ListDataModel(contacts); } public DataModel getFeatures() { contacts = contactsDao.getContacts(); features = new ListDataModel(contacts); return features; } public List getContacts() {
contacts = contactsDao.getContacts(); features = new ListDataModel(contacts); return contacts; } public String editContact() { setSelectedContact(contacts.get(features.getRowIndex())); setCreation(false); return "success"; } public String create() { selectedContact = new Contacts(); setCreation(true); return "success"; } public String saveContact() { contact = new Contacts(); contact.setCIN(selectedContact.getCIN()); contact.setFirstName(selectedContact.getFirstName()); contact.setLastName(selectedContact.getLastName()); contact.setEmail(selectedContact.getEmail()); try { contactsDao.create(selectedContact); } catch (DataIntegrityViolationException e) { FacesContext.getCurrentInstance().addMessage( "Exist", new FacesMessage( "L'identifiant saisi existe déjà dans la base")); return "echec"; } return "success"; } public String updateContact() { setCreation(false); contactsDao.update(selectedContact); return "success"; } public String deleteContact() { setCreation(false); contactsDao.delete(contacts.get(features.getRowIndex())); return "success"; } public String getContactById() { setCreation(false); setSelectedContact(contactsDao.findById(selectedContact.getCIN())); listeContrats = contratsDao.findContratsByContact(selectedContact .getCIN()); return "success"; } public ContactsDao getContactDao() { return contactsDao; } public void setContactsDao(ContactsDao contactsDao) { this.contactsDao = contactsDao; } public Contacts getSelectedContact() { return selectedContact; }
public void setSelectedContact(Contacts selectedContact) { this.selectedContact = selectedContact; } public boolean isCreation() { return creation; } public void setCreation( boolean creation) { this.creation = creation; } public ContratsDao getContratsDao() { return contratsDao; } public void setContratsDao(ContratsDao contratsDao) { this.contratsDao = contratsDao; } public List getListeContrats() { return listeContrats; } public void setListeContrats(List listeContrats) { this.listeContrats = listeContrats; }
}
Il ne nous reste plus que le fichier de configuration « faces-config.xml » org.springframework.web.jsf.el.SpringBeanFacesELResolver /index.xhtml#{contactCtr.create}success/onecontact.xhtml/ListContact.xhtml#{contactCtr.editContact}success/onecontact.xhtml#{contactCtr.create}success/onecontact.xhtml/onecontact.xhtml#{contactCtr.saveContact}
8- Sécurité avec Spring Nous allons maintenant sécuriser l’application en demandant à l’utilisateur de se connecter avant de pouvoir accéder à l’application. Pour cela, il suffit de modifier le fichier « web.xml » en ajoutant les balises suivantes : springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy springSecurityFilterChain /*FORWARDREQUEST
Nous allons ensuite créer le fichier « ApplicationContext-security.xml »
Le login et le mot de passe ainsi que les rôles des utilisateurs sont configurés en dur dans le fichier (login : user, mot de passe : password). On définit aussi les règles d’accès aux pages de l’application à l’aide de l’élément « intercept-url ». Il s'agit simplement d'associer un pattern d'URL à un ou plusieurs rôles (séparation avec une virgule). A noter que les patterns d'URL sont traités dans l'ordre de déclaration : si /** était déclaré en premier, les autres patterns ne seraient pas vérifiés. La page de login personnalisée est spécifiée à l’aide de l’attribut « login-page ». A noter que pour cette page, nous avons une règle d’interception avec « filters= « none » » pour que la page de login ne soit pas filtrée par Spring Security. Sans cette règle, l'application tombe dans une boucle infinie qui tente d'afficher la page « login.jsf », mais qui nécessiterait aussi d'être authentifiée (suivant la dernière règle avec le pattern /**). Nous définissons ensuite la page « login.xhtml » ainsi que la page « loginFailure.xhtml ». La page de login doit respecter certaines règles : • • •
action => j_spring_security_check nom input identifiant => j_username nom input mot de passe => j_password