lundi 29 octobre 2012

Java Serveur App 8 : JPA et les relations

Relations

Une entité peut contenir des champs entier, des chaines de caractères ou encore des dates. 

Maintenant, nous allons aussi vouloir permettre à une entité de faire référence à une autre entité. C'est là qu'interviennent les relations.

OneToMany et ManyToOne

Un restaurant a plusieurs plats. Chaque plat n'appartient qu'à ce restaurant. On a donc une relation de 1 restaurant vers plusieurs plats. Dans l'autre sens de cette relation, plusieurs plats sont reliés à un restaurant. Cela nous mène au code suivant:



package com.deguet.model;

import java.io.Serializable;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;


@Entity
public class Restaurant implements Serializable {
 private static final long serialVersionUID = 1L;

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private int id;

 private String name;

 //bi-directional many-to-one association to FoodItem
 @OneToMany(fetch = FetchType.LAZY, cascade={CascadeType.ALL}, orphanRemoval = true)
 private List<FoodItem> foodItems;

    public Restaurant() {
    }

 public int getId() {
  return this.id;
 }

 public void setId(int idRestaurant) {
  this.id = idRestaurant;
 }

 public String getName() {
  return this.name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public List<FoodItem> getFoodItems() {
  return this.foodItems;
 }

 public void setFoodItems(List<FoodItem> foodItems) {
  this.foodItems = foodItems;
 }
 
}
Le champs qui représente la relation est foodItems qui contiendra la liste des plats du restaurant.

Le code d'un plat (FoodItem) contient également une référence vers le restaurant auquel ce plat appartient. C'est ce qu'on appellera une relation bidirectionnelle.

package com.deguet.model;

import java.io.Serializable;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;

import com.restooo.model.Basket;
import com.restooo.model.Restaurant;

@Entity
public class FoodItem implements Serializable {
 private static final long serialVersionUID = 1L;

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private int id;

 private String name;

 private String description;

 private Double price;

 //bi-directional many-to-many association to Basket
 @ManyToMany(mappedBy="foodItems")
 private List<Basket> baskets;

 //bi-directional many-to-one association to Restaurant
    @ManyToOne
 private Restaurant restaurant;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public FoodItem() {
 }

 public int getId() {
  return this.id;
 }

 public void setId(int idFoodItem) {
  this.id = idFoodItem;
 }

 public String getDescription() {
  return this.description;
 }

 public void setDescription(String description) {
  this.description = description;
 }

 public Double getPrice() {
  return price;
 }

 public void setPrice(Double price) {
  this.price = price;
 }

 public List<Basket> getBaskets() {
  return baskets;
 }

 public void setBaskets(List<Basket> baskets) {
  this.baskets = baskets;
 }

 public Restaurant getRestaurant() {
  return restaurant;
 }

 public void setRestaurant(Restaurant restaurant) {
  this.restaurant = restaurant;
 }

}



ManyToMany

Une commande contient plusieurs plats en quantité différentes. Nous allons appeler notre commande Basket (Order est un mot réservé SQL). Nous y voyons le côté principal est celui de la commande à laquelle on ajoutera des plats se déclare par le champs foodItems. 


package com.deguet.model;

import java.io.Serializable;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;


/**
 * The persistent class for the Basket database table.
 * 
 */
@Entity
public class Basket implements Serializable {
 private static final long serialVersionUID = 1L;

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private int id;

 private String test;
 
 //bi-directional many-to-many association to FoodItem Owning SIDE
 @ManyToMany
    @JoinTable(
           name="basket_fooditem",
           joinColumns={@JoinColumn(name="BASKET_ID", referencedColumnName="id")},
           inverseJoinColumns={@JoinColumn(name="FOODITEM_ID", referencedColumnName="id")})
 private List<FoodItem> foodItems;

    public Basket() {
    }

 public int getId() {
  return this.id;
 }

 public void setId(int id) {
  this.id = id;
 }

 public List<FoodItem> getFoodItems() {
  return this.foodItems;
 }

 public void setFoodItems(List<FoodItem> foodItems) {
  this.foodItems = foodItems;
 }

 public String getTest() {
  return test;
 }

 public void setTest(String test) {
  this.test = test;
 }
 
}

Lazy fetch

Les relations et les méthodes getXXX correspondantes permettent une méthode de chargement retardée appelée Lazy Loading en anglais. L'objet relié n'est chargé que quand la méthode getXXX est appelée.


Exemple: un restaurant avec 350 plats est chargé en mémoire. La requête récupère juste le restaurant. Ensuite, si on exécute un getFoodItems() sur le champ contenant les plats, une autre requête est faite à la base de données.


Création d'un restaurant et de ses plats

Pour illustrer la création d'entité ainsi que d'autres entités liées, nous allons créer un restaurant avec plusieurs centaines de plats à travers notre code. Pour cela nous allons créer une classe InitialLoad avec le code suivant:

package com.deguet;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import com.deguet.model.FoodItem;
import com.deguet.model.Restaurant;

public class InitialLoad {

 
 private static Facade<Restaurant> rf = new Facade<Restaurant>(Restaurant.class);
 
 public static void load(){
  String[] types = {"Pizza ","Sushi ","Patate ","Tacos ","Pita ","Cassoulet ","Paela ","Quiche "};
  String[] noms  = {"Réjean ","Paulo ","Joris ","Claudette ","Jean ","Paul ","Pierre ","Pedro ","Bertha ","Johnny "};
  String[] plats = {"du jour ","fancy ","italiana ","québécoise ","pour trois","à la mode de chez nous",
    "espanola","qui tue ", "qui pique","pas cher","pour deux","pour toute la famille","du Colonel Sanders"};
  Random rand = new Random();
  for (String type : types){
   for (String nom : noms){
    Restaurant r = new Restaurant();
    List<FoodItem> dishes = new ArrayList<FoodItem>();
    r.setName(type+nom);
    r.setFoodItems(dishes);
    rf.create(r);
    Collections.shuffle(Arrays.asList(plats));
    for (int i = 0 ; i < 5 ; i++)
    {
     String plat = plats[i];
     FoodItem item = new FoodItem();
     item.setRestaurant(r);
     item.setDescription("Bla ingrédients ...");
     item.setName(type+plat);
     item.setPrice((rand.nextInt(100)+50)/10.0);
     dishes.add(item);
    }
    rf.edit(r);
   }
  }
 }

}


persistence.xml

Si vous oubliez de mentionner les nouvelles classes à JPA dans votre persistence.xml, vous aurez un message d'erreur "bli.bla.Blo  is not a known entity type".


Fichiers et lecture supplémentaire

Vous trouverez ici le zip du projet.

Une lecture supplémentaire fortement recommandée est le wikibook sur JPA.

mardi 23 octobre 2012

Java Serveur App 7 : JPA et CRUD

Modèle : JPA, les ORM

Le modèle correspond aux données de notre système. Il s’agit de savoir comment stocker l’information. Un Object Relationship Mapper s'assure de faire le lien entre un modèle objet et une base de données relationnelle. Le lien peut se faire dans 2 directions:


  • Créer automatiquement des classes objets à partir d'une base de données existante.
  • Créer des classes objets et demander la génération automatique d'une base de données relationnelle qui stockera ces objets.
C'est cette seconde possibilité que nous allons utiliser dans notre application.

pom.xml : EclipseLink

EclipseLink est l'implémentation de référence de la norme JPA. Il s'agit donc d'un des nombreux moteurs permettant de stocker des objets en base de données. Il existe d'autres implémentations, la plus connue étant certainement Hibernate. Afin d'inclure EclipseLink, il va nous falloir ajouter un repository dans notre pom.xml. Ajoutez la balise suivante juste avant la balise dependencies de votre pom.xml:

<repositories>
 <repository>
  <id>eclipselink</id>
  <url>http://www.eclipse.org/downloads/download.php?r=1&amp;nf=1&amp;file=/rt/eclipselink/maven.repo/</url>
 </repository>
</repositories>

Ensuite, ajoutez la dépendance suivante:
<dependency>
 <groupId>org.eclipse.persistence</groupId>
 <artifactId>eclipselink</artifactId>
 <version>2.4.0</version>
</dependency>

pom.xml : Derby (Apache)

Apache Derby est une base de données qui peut être embarquée. Autrement dit, elle se lance quand le serveur est lancé. Nous l'utiliserons ici pour simplifier puisqu'il n'y a pas besoin d'installer de serveur de base de données séparé.


<dependency>
 <groupId>org.apache.derby</groupId>
 <artifactId>derby</artifactId>
 <version>10.9.1.0</version>
</dependency>

<dependency>
 <groupId>org.apache.derby</groupId>
 <artifactId>derbyclient</artifactId>
 <version>10.9.1.0</version>
</dependency>
JPA, première entité

JPA fonctionne en déclarant des classes comme des entités. Une entité va être un classe Java pour laquelle nous allons déclarer un champ identifiant. Voici le code de notre première entité:


package com.deguet.model;

import java.io.Serializable;
import javax.persistence.*;

@Entity
public class Test implements Serializable {
 
 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private int id;

 private String content;
 
 public int getId(){return this.id;}
 public void setId(int idImages) {this.id = idImages;}
 
 public String getContent() {return content;}
 public void setContent(String content) {this.content = content;}
}

Cette classe est placée dans le package com.deguet.model. La différence la plus importante est ici la présence d'un identifiant id. En effet, la base de données a besoin d'un identifiant explicite. (ce n'est pas le cas en objet, l'objet étant identifié par sa référence, implicite).

persistence.xml

JPA se repose sur le fichier persistence.xml pour décrire la base de données à utiliser et les classe Java faisant partie du modèle. Le fichier persistence.xml doit se trouver dans WEB-INF/classes/META-INF/persistence.xml.


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
  xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="RestoPresto">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <class>com.deguet.model.Test</class>
    <properties>
      <property name="javax.persistence.jdbc.password" value="app" />
      <property name="javax.persistence.jdbc.user" value="app" />
      <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver" />
      <property name="javax.persistence.jdbc.url" value="jdbc:derby:databasederby/resto;create=true" />
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
      <property name="eclipselink.drop-ddl-jdbc-file-name" value="ddl.jdbc"/>
      <property name="eclipselink.ddl-generation.output-mode" value="both" />
      <property name="eclipselink.logging.level" value="INFO"/>
    </properties>
  </persistence-unit>
</persistence>

Nous avons défini une unité de persistence. Vous remarquerez les points suivants:

  • Chaque classe d'entité (ici Test) doit être déclarée dans le fichier.
  • l'URL pour la base de données derby est en fait un chemin vers le répertoire databasederby qui sera créé dans le répertoire de votre projet.
  • la propriété ddl-generation indique à EclipseLink qu'il doit détruire et recréer la base de données à chaque exécution (drop and create).
  • la propriété drop-ddl-jdbc-file-name permet de récupérer les scripts de création de la base (dans le fichier ddl.jdbc) afin de déboguer un éventuel problème à la génération de la base.


Facade CRUD pour notre entité


JPA se repose sur un EntityManager pour effectuer les opérations en base de données.

Les opérations CRUD (Create Retrieve Update Delete) nécessite d'accéder à un EntityManager afin d'exécuter les requêtes correspondantes. Afin de faciliter la compréhension voici une classe permettant d'effectuer les opérations de base (création, mise à jour, récupération ou suppression) pour une entité T:



package com.deguet;

import java.util.List;
import javax.persistence.*;

public class Facade<T> {
 
  Class<T> entityClass;

  @PersistenceContext(unitName = "RestoPresto")
  private static EntityManager em;
  protected EntityManager getEntityManager() {return em;}

  public Facade(Class<T> entityClass) {
    if (em == null){
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("RestoPresto");
      em = emf.createEntityManager();
    }
    this.entityClass = entityClass;
  }

  public void create(T entity) {
    EntityManager em = getEntityManager();
    em.getTransaction().begin();
    if (!em.contains(entity)) {
      em.persist(entity);
      em.flush();
    }
    em.getTransaction().commit();
  }

  public void edit(T entity) {
    EntityManager em = getEntityManager();
    em.getTransaction().begin();
    em.merge(entity);
    em.flush();
    em.getTransaction().commit();
  }

  public void remove(T entity) {
    EntityManager em = getEntityManager();
    em.getTransaction().begin();
    T toDelete = em.merge(entity);
    em.remove(toDelete);
    em.flush();
    em.getTransaction().commit();
  }

  public T find(Object id) {
    return getEntityManager().find(entityClass, id);
  }

  public List<T> findAll() {
    javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
    cq.select(cq.from(entityClass));
    return getEntityManager().createQuery(cq).getResultList();
  }

  public int count() {
    javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
    javax.persistence.criteria.Root<t> rt = cq.from(entityClass);
    cq.select(getEntityManager().getCriteriaBuilder().count(rt));
    javax.persistence.Query q = getEntityManager().createQuery(cq);
    return ((Long) q.getSingleResult()).intValue();
  }
}

Test dans le contrôleur


Pour tester que tout fonctionne correctement, nous allons maintenant tester la création d'un objet puis la récupération de la liste d'objets dans un nouveau motif URL dans notre contrôleur.

Tout d'abord vous devez créer une facade pour notre entité avec la ligne suivante:


Facade<Test> facade = new Facade<Test>(Test.class);


Ensuite il faut créer le motif URL permettant de déclencher la création d'un nouvel objet, sa sauvegarde puis la récupération de la liste complète.

if (action.equals("/testCharge")){
System.out.println("Controler GET acheter ");
Test t = new Test();
t.setContent("bli blo " + System.currentTimeMillis());
facade.create(t);
List<Test> tests = facade.findAll();
for (Test test : tests){
System.out.println("Le test est " + test);
}
return;
}

En rechargeant plusieurs fois la page, http://localhost:7070/testCharge vous verrez la liste d'entités s'allonger.

Fichiers du projet avec une entité

Vous trouverez le projet avec l'écriture d'une entité en base de données ici.



dimanche 21 octobre 2012

Java Serveur App 6 : attributs de requête/session/contexte

Jusqu'ici, nous avons vu:
  • les pages JSP et leur balises
  • les motifs URL et les liens avec une Servlet contrôleur
  • les différents mode de redirection
Le problème est que nous n'avons pas de moyen de partager des objets Java entre différentes pages ou encore entre le contrôleur et une page. Nous allons voir ici comment mettre en place un tel partage.

Gestion des attributs

La requête HTTP (dans les pages ou dans la contrôleur) dispose de deux méthodes appelées setAttribute et getAttribute. Ces méthodes permettent de stocker ou de récupérer un objet dans dans la requête.

L'exemple classique est le suivant:
  1. Votre classe contrôleur veut passer un objet nommé panier de classe Panier à la vue JSP.
  2. Dans le code du contrôleur, vous effectuez l'instruction

    request.setAttribute("panier",panier);

  3. Dans le code de la page JSP, vous effectuez l'instruction

    Panier recupere = (Panier) request.getAttribute("panier");


Requête, session et contexte

Il existe plusieurs portées pour les attributs :

  • la requête HttpRequest permet de gérer des attributs, le cas le plus courant est une servlet qui passe un objet à une page JSP dans le cas d'une redirection via le requestDispatcher.
  • la session HttpSession permet de gérer des attributs pour l'ensemble d'une session, c'est à dire l'ensemble des pages de votre application pour un client.
  • le contexte ServletContext permet des attributs partagés pour toutes les sessions de  l'application.
Pour chaque objet que vous souhaitez partager via un attribut, il faut déterminer quelle sera 
  1. sa durée de vie: un attribut de contexte dure tant qu'on ne le supprime pas ou qu'on n'arrête pas le serveur. Un attribut de requête disparaît avec la requête.
  2. son utilisateur: si un élément est spécifique à un client, ce sera soit la requête soit la session. Sinon ce sera le contexte.
Exemple de la commande en session

Nous allons voir comment gérer un objet commande (similaire à un panier d'achats) dans notre session. Le code source de la classe Java modélisant une commande sera : 

package com.deguet;

import java.util.HashMap;
import java.util.Map;

public class CommandeEnSession {

 public Map<String, Integer> items;
 
 public CommandeEnSession(){
  items = new HashMap<String, Integer>();
 }
 
 public void addItem(String name){
  if (items.containsKey(name)) items.put(name, items.get(name)+1);
  else items.put(name, 1);
 }
 
 public int totalQuantity(){
  int result = 0;
  for (String name: items.keySet()) result += items.get(name);
  return result;
 }
 
 public int itemQuantity(){
  return items.size();
 }
 
 public String toString(){
  String result = "";
  for (String name: items.keySet())
   result += "("+name+" : "+items.get(name)+")";
  return result;
 }
}


Maintenant, pour ajouter des éléments à cette commande, l'action dans les vues devra ressembler à :


<a href="ajouterCommande?name=Pizza pour trois">Ajouter</a>

Cette action va déclencher le motif URL "ajouterCommande" avec comme paramètre le nom du plat. Il faut écrire le code contrôleur qui réagira à cette action:

if (action.equals("/ajouterCommande")){
 // récupération du paramètre de l'ajout, le plat à ajouter
 String plat = request.getParameter("name");
 System.out.println("Controler GET ajouter le plat "+plat);
 // si la commande n'existe pas dans la session, la créer
 CommandeEnSession commande = (CommandeEnSession) request.getSession().getAttribute("commande");
 if (commande == null) {
  commande = new CommandeEnSession();
  request.getSession().setAttribute("commande",commande);
 }
 // ajouter le plat
 commande.addItem(plat);
 System.out.println("La commande est  "+commande);
 // rediriger vers le menu
 response.sendRedirect("/menu");
 return;
}




Afin de vérifier que tout fonctionne correctement, nous allons maintenant voir comment afficher le panier dans la page commande.jsp:

<table class="table table-striped">
 <thead>
  <tr>
   <th>Plat</th>
   <th>Quantité</th>
  </tr>
 </thead>
 <tbody>
 <% 
 // si il existe une commande
 CommandeEnSession commande = (CommandeEnSession) session.getAttribute("commande");
 if (commande != null){
  for (String name : commande.items.keySet()){
   Integer qty = commande.items.get(name);
   %>
   <tr>
    <td><%=name %></td>
    <td><%=qty %></td> 
   </tr>
   <%
  }
 }
 %>       
 </tbody>
</table>


Nous avons vu comment créer un objet d'une classe Java, y ajouter des items et afficher son état dans une page JSP.




Fichier du restaurant avec commande gérée en session

Vous trouverez ici l'exemple du projet avec une gestion simple d'une commande en session. Vous trouverez également un widget dans l'entête JSPF permettant d'afficher l'état de la commande si il en existe une.

mardi 16 octobre 2012

Java Serveur App 5 : Navigation


Navigation


La navigation, c'est principalement la manière dont notre application gère le passage d'une page à une autre tout en permettant l'exécution de code Java entre elles. Nous allons voir comment permettre certains bons points:


  • comment rendre les pages inaccessibles directement
  • comment en contrôler l'accès via le contrôleur et exécuter du code Java
  • comment conserver des URLs propres au cours de la navigation


WEB-INF et les pages cachées

Pour cacher une page JSP ou toute autre resource, il suffit de placer dans le répertoire WEB-INF. Pour tester, déplacer une de vos pages JSP dans WEB-INF. Essayez d’y accéder avec l’URL (dans mon cas, j'ai déplacé le fichier commande.jsp):

http://localhost:7070/WEB-INF/commande.jsp


Vous devriez alors obtenir une erreur 404.


Accéder aux pages cachées via le contrôleur

Dans le contrôleur, nous allons pouvoir utiliser une instruction du type:


request.getRequestDispatcher("WEB-INF/commande.jsp").forward(request, response);

Cette redirection se fait au niveau du serveur et permet d’accéder aux pages contenues dans le WEB-INF. Nous allons modifier le doGet introduit précédemment:




@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) 
  throws ServletException, IOException  {
 // get the URL pattern, the action
 String action = request.getServletPath();
 if (action.equals("/motif")){
  System.out.println("Controler GET motif ");
  return;
 }
 if (action.equals("/acheter")){
  System.out.println("Controler GET acheter ");
  request.getRequestDispatcher("WEB-INF/commande.jsp").forward(request, response);
  return;
 }
}



Maintenant, vous devriez pouvoir accéder à votre page JSP via l'URL : http://localhost:7070/acheter



Redirections vers des JSP


Notre contrôleur va pouvoir rediriger sa requête de deux manières différentes: 
  • response.sendRedirect("url de redirection");
  • request.getRequestDispatcher("url").forward(request,response); que nous venons de voir.
Voyons un peu quelles sont les différences ...
Redirections par sendRedirect


Une redirection par sendRedirect est effectuée via la réponse HTTP. Les événements sont donc les suivants:
  1. La servlet reçoit la requête du navigateur
  2. La servlet répond avec sa réponse qui contient une demande de redirection vers une url
  3. Le navigateur envoie une nouvelle requête au serveur sur cette URL (et donc potentiellement repasse par le contrôleur).
Les conséquences sont les suivantes:
  • L'URL en question doit être accessible du navigateur
  • La redirection change l'URL visible dans le navigateur
  • Provoque un nouveau cycle requête/réponse HTTP
  • Les paramètres de la première requête HTTP sont perdus pour la seconde requête (sauf si vous les avez spécifiés dans l'URL de redirection en méthode GET)
Redirections par forward


L'accès au RequestDispatcher permet d'accéder à des URLs serveur comme des pages JSP contenues dans le répertoire WEB-INF. D'un navigateur, les fichiers contenus dans le répertoire WEB-INF de votre webapp ne sont pas accessibles. Par contre, on pourra effectuer une redirection du type suivant:


request.getRequestDispatcher("WEB-INF/page.jsp").forward(request,response);

Ici, le RequestDispatcher (qui est un objet du serveur) va faire suivre à la "page.jsp" en lui passant la requête et la réponse initiales. C'est page.jsp qui produire la réponse HTTP.


En conséquence:

  • On peut cacher une page "WEB-INF/rep/sous-rep/page.jsp" avec un URL pattern plus propre "/commande", l'URL n'est pas réécrite dans la barre d'adresses du navigateur
  • Les pages JSP ne sont pas directement accessible ce qui permet d'effectuer des vérifications en Java, des accès BD etc. dans le contrôleur avant d'afficher la page JSP.
  • Les paramètres initiaux sont envoyés à la page JSP.
Vues + contrôleur : un mot sur MVC

Voici une première version d'une approche MVC web :

  • Chaque action, clique sur un bouton, envoi de formulaire référence un motif URL (ex: "/payer")
  • Chaque motif URL pointe sur le contrôleur, une servlet en Java.
  • Dans le code Java, on valide que les paramètres sont corrects, on lit ou on écrit en base de données ce qui est nécessaire au modèle, on écrit dans la session les informations nécessaire etc.
  • Une fois les traitements effectués, on redirige vers une vue JSP qui va afficher les informations nécessaires prises dans les attributs de la requête ou de la session.
  • Cette vue, qui finalement génère une page HTML peut contenir des liens ou des boutons qui à leur tour pointe vers des motifs URL et provoquent des actions qui déclencheront un passage dans le contrôleur.
On peut donc implanter une bonne part de l'application sans le modèle en s'assurant que les liens entre page donnent bien lieu à un passage dans le contrôleur. Cet ensemble Vues + contrôleur permet d'implanter la navigation dans notre application.

Résumé

Nous avons maintenant un squelette d’application Vues + Contrôleur. Pour avoir un squelette MVC, il ne nous manque plus que le modèle (de données).


Fichiers

Le projet que vous trouverez ici contient maintenant un contrôleur et quelques motifs URL. 

Pour compléter la navigation de votre application (pas de M)VC, il suffit de:

  • Intégrer toutes les actions que votre utilisateur peut déclencher (voirMaCommande, entrerCoordonnees, payerCommande, voirMenu, voirRestaurants etc.) dans votre web.xml. 
  • Compléter votre contrôleur pour avoir un bloc dédié dans le doXXX approprié (GET ou POST) pour chaque action.
  • Rediriger vers la vue JSP correspondante de votre application.



lundi 15 octobre 2012

Java Serveur App 4 : Contrôleur

Contrôleur : servlet, WEB-INF et redirections

Création d'un répertoire de sources Java

S'il n'existe pas encore, vous allez créer un répertoire de sources Java.
Cliquez droit sur le projet et demandez un nouveau répertoire de sources:



Ensuite indiquez le chemin src/main/java. Il s'agit d'une convention dans tous les projets Maven.




Création d'un package de sources Java

En cliquant droit sur le répertoire de sources tout juste créé, vous allez pouvoir y créer un package:





Le nom de votre package correspond souvent au nom du site que vous développez: pour le site www.blabla.com, le package est habituellement appelé com.blabla. Ici mon package sera com.deguet

Création de votre première Servlet Java: le contrôleur


Nous allons maintenant créer une servlet. Un servlet est une classe Java permettant de répondre à des requêtes HTTP. Votre Servlet va étendre la classe HttpServlet. Nous allons la créer dans le package tout juste créé:




Ensuite, nous allons nommer la Servlet Controler et spécifier la classe dont elle hérite (javax.servlet.http.HttpServlet):




Il faut maintenant surcharger (override) une des méthodes de la classe HttpServlet pour définir le comportement de notre contrôleur en insérant le code suivant dans notre classe:


@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException 
{
 System.out.println("Controler ::: GET");
}

Pour l'instant, nous demandons juste à notre contrôleur de nous avertir quand il est sollicité.

Création d'un motif URL

Un motif URL est une chaîne ressemblant à "/motif" qui va correspondre à une URL sur votre serveur comme http://localhost:7070/motif


Un serveur Java va vous permettre de diriger un motif URL vers une Servlet défini par une classe. Ensuite, toutes les requêtes sur ce motif déclencheront les méthodes doXXX de la Servlet. Exemple, si une requête de type GET est envoyée au serveur, elle déclenchera la méthode doGet de notre classe.

Mapping et web.xml

Localisez le fichier web.xml dans le répertoire dans le répertoire WEB-INF de votre répertoire webapp. Ouvrez le et ajouter le code suivant dans la balise web-app après la balise display-name.



 <servlet>
  <servlet-name>Controller</servlet-name>
  <servlet-class>com.deguet.Controler</servlet-class>
 </servlet>

 <servlet-mapping>
  <servlet-name>Controller</servlet-name>
  <url-pattern>motif</url-pattern>
 </servlet-mapping>


Le premier élément est la définition de votre servlet qui contient un nom et une classe. Il est possible de définir plusieurs servlets avec des noms différents utilisant une même classe.


Le second élément associe un motif URL avec un servlet, ici le Controller. On peut lier autant de motifs que l'on souhaite à une servlet.

Test du motif URL.

Redémarrez votre serveur et entrez l'URL http://localhost:7070/motif dans votre navigateur. Le serveur va vous servir une page blanche. Toutefois, si vous observez la console de votre serveur, vous devriez observer le message
"Controler ::: GET"

Multiples motifs et traitement dans le contrôleur.

L'idée du contrôleur est d'être le point d'entrée de chaque action de l'utilisateur. Cela tient en deux étapes:


  1. Ajouter d'autres motifs URL qui seront redirigés vers notre contrôleur.
  2. Organiser le code de nos méthodes doXXX pour ne pas faire le même traitement pour chaque motif.
Pour le point 1, il suffit de créer un nouveau servlet-mapping pour de nouveaux motifs comme par exemple "/acheter":

<servlet-mapping>
  <servlet-name>Controller</servlet-name>
  <url-pattern>acheter</url-pattern>
 </servlet-mapping>

Pour le point 2, nous allons reprendre le code du doGet :




@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) 
 throws ServletException, IOException 
{
 // get the URL pattern, the action
 String action = request.getServletPath();
 if (action.equals("/motif")){
  System.out.println("Controler GET motif ");
  return;
 }
 if (action.equals("/acheter")){
  System.out.println("Controler GET acheter ");
  return;
 }
}


On peut maintenant effectuer le traitement approprié pour chaque motif dans le bloc approprié. Testez que l'affichage correct se fait sur les URLs:
http://localhost:7070/motif
http://localhost:7070/acheter

Étape suivante

Le contrôleur de notre application va être une servlet vers laquelle tous les motifs nécessaires vont être associés. L'avantage est de centraliser le code qui réagit aux actions des utilisateurs. Nous allons voir comment charger une vue JSP après être passé dans notre servlet contrôleur.


Fichiers

Vous trouverez les fichiers après cette étape ici.


mardi 9 octobre 2012

Java Serveur App 3 : vues dynamiques


Pages dynamiques: JSP

Une page HTML est une page JSP valide, c'est une page statique. Il suffit de renommer un fichier .html en .jsp. Par exemple, renommez votre fichier index.html en index.jsp.

L’inverse est faux, une page JSP contient souvent des passages incompréhensibles tels quels pour un navigateur. Ces passages sont des traitements à effectuer par le serveur Java. Ce sont ces traitements qui rendent une page dynamique.

L'élément de base est un scriplet. Un scriptlet est délimitée par <% et %>. Entre les deux, vous pouvez écrire du code Java classique. Surtout vous pouvez inclure un “out.println()” qui va écrire dans la page.


Voici un exemple minimal, le code Java est en rouge:
<html>
   <head>
       <title>JSP Page</title>
   </head>
   <body>
       <h1>
           <%
               out.println("Hello World "+(99*8));
           %>
       </h1>
   </body>
</html>

Dans votre page, essayez d'inclure la scriplet suivante: 
<% out.println(new Date()); %>

Cette instruction devrait permettre d'inclure la date d'aujourd'hui. Cependant, Eclipse vous mentionnera qu’il manque un import pour la classe Date.

La solution rapide est de remplacer:
<% out.println(new Date()); %>
par
<% out.println(new java.util.Date()); %>

Vous verrez alors la date s'afficher dans la page. 
Note: "out" désigne la page web produite, "System.out" permet d'écrire dans la console du serveur Java.

Imbrication dans les JSP

Un usage très courant des JSP est de générer un élément HTML pour une liste Java. L'exemple suivant permet par exemple de créer un morceau de code pour i allant de 0 à 4.

<body>
<%
for (int i = 0 ; i<5 ; i++){
%>
<p>
un exemple pour taper du HTML facile dans une boucle
avec un index <%= i %>
</p>
<%
}
%>
</body>


On peut inclure un passage de HTML dans une scriplet et rouvrir une scriplet et ainsi de suite. Il n'y a pas de limite à l'imbrication dans une page. Attention quand même à bien indenter votre code, la lisibilité du code imbriquée devient vite difficile.


Import dans des pages JSP

Un import permet de ne pas avoir à taper le nom complet d'une classe. En tout début de fichier JSP, vous pouvez ajouter:
<%@ page import="java.util.Date" %>

Une fois la classe importée, vous pourrez utiliser le nom simple de la classe soit "Date" au lieu java.util.Date. Attention, l'import de rend pas Date accessible, elle est toujours accessible; il facilite juste l'écriture du nom de la classe en rendant le nom de package (java.util dans mon exemple) implicite.

Différentes balises JSP

Il existe plusieurs couples de balises utilisées par JSP, voici les principales:

<%java code %> 
permet d'exécuter le code Java (scriptlet)

<%= expression %> 
affiche directement la valeur de l'expression. Ne nécessite pas de ;

<%-- commentaire --%> 
commentaires multi-lignes

<%! int x = 10; %> 
déclare un champ ou méthode pour l'ensemble de la page

<%@ page import="java.util.Date%> 
permet les importations, souvent en début de page

Objets implicites JSP

Une page JSP permet un accès à certains objets créés implicitement pour le développeur. Par exemple l'instruction "out.println();" utilisait l'objet out de la classe javax.servlet.jsp.JspWriter et qui permet d'écrire la page produite.


Un autre objet intéressant est request qui permet d'accéder à la requête HTTP et en particulier ses paramètres. Par exemple, la page suivante:

<html>
   <body>
       <h1>
           <%
               out.println("Bonjour" + request.getParameter("id"));
           %>
       </h1>
   </body>
</html>

Affichera le paramètre id. Pour l'URL http://localhost:7070/fichier.jsp?id=75, la page affichera "Bonjour 15".

Finalement, l'objet session permet de stocker des informations dans la session utilisateur (ces informations sont partagées entre les pages) à travers ses méthodes getAttribute et setAttribute. Par exemple, si une première page contient le code suivant:


<html>
   <body>
       <h1>
           <%
               session.setAttribute("count",4);
           %>
       </h1>
   </body>
</html>

Une deuxième page pourra accéder à cette information ainsi


<html>
   <body>
       <h1>
           <%
               out.println("Le compteur vaut " + session.getAttribute("count"));
           %>
       </h1>
   </body>
</html>

Nous reviendrons plus tard sur le fonctionnement de la session.


Fragments

La plupart des pages d'un site vont partager de grandes portions de code. Vous souhaiterez sans doute mettre en commun ses portions afin de faciliter le changement d'un menu (par exemple) à travers toutes vos pages.

La norme JSP offre cette possibilité à travers les fragments JSP. Il suffit de créer un fichier d'extension jspf contenant la portion de page JSP en question. Ensuite pour insérer ce fragment dans une page (et souvent dans plusieurs pages) il suffit de répéter le passage suivant:


<%@ include file="chemin/vers/fichier.jspf" %>

La plupart du temps, on placera les fragments dans un répertoire à l'intérieur du répertoire WEB-INF dans webapp. Cela permet de cacher les fragments et de les ranger dans un répertoire dédié.

Dans votre projet, identifier les parties communes à toutes vos pages et créer un fichier jspf pour chacune d'elles. remplacer le code par le include nécessaire.


Lecture

Une description plus en détails des JSP:

http://fr.wikipedia.org/wiki/JavaServer_Pages


Vous trouverez un projet fonctionnel contenant les fichiers décrits dans cette étape ici.