JBoss Inc. a annoncé la sortie de JBoss Seam à la mi-septembre.
Seam (couture,soudure en français) est un framework applicatif pour Java EE 5 qui regroupe les EJB 3.0 et Java Server Faces (JSF) pour permettre
d’avoir un environement prêt pour déveloper une application rapidement.
Seam se base sur les annotations et l’architecture EJB 3.0 pour intégrer la partie présentation ainsi que la gestion d’un workflow, le tout géré par un conteneur.
Concernant la partie vue, Seam utilise JSF ainsi que les Facelets pour que l’écriture de la partie vue soit orientée composant. Basé sur du xhtml, la mise en page
et la création des pages est donc simple. Côté persistence, grâce à EJB 3.0 il est facile de persister les données vers une base de données grâce à l’EntityManager des EJB 3.0.
J’ai téléchargé et installé JBoss Seam en 15mn. Facile à installer, j’ai ensuite configuré un nouveau projet sous IDEA IntelliJ (Best IDE for Java 2005) pour commencer à regarder le moteur de la machine… Tout d’abord voici comment l’installer rapidement pour tester (traduction du site de JBoss + mes commentaires):
- Installer Java 5 (obligatoire pour les EJB 3.0) via le site de SUN
- Installer Jakarta Apache ANT si nécessaire (1.6.2 minimum)
- Télécharger JBoss AS 4.0.3 en sélectionnant le profil EJB3.0 (Installer JBoss AS 4.0.3 avec Java Web Start)
- Tester et démarrer JBoss via le script de démarrage run.bat dans %JBOSS_HOME%/bin. Vérifiez que JBoss utilise bien Java 5 si vous avez plusieurs JDK installés.
- Téléchargez JBoss Seam et le décompresser dans un répertoire de travail en dehors de l’arborescence du serveur JBoss AS.
- Editer et configurer le fichier build.properties de votre répertoire Seam en spécifiant le chemin d’installation de JBoss AS
- Dans le répertoire jboss-seam/examples/booking directory, tapez ant deploy pour compiler et déployer la démo Hotel Booking.
- Lancer un navigateur et essayer de vous connecter à l’adresse http://localhost:8080/seam-booking/ (sinon aussi dispo ici Demo sur le site de JBoss)
- Créez un compte en cliquant sur Register pour entrer ensuite sur l’interface principale.
JBoss Seam propose de gérer le contexte d’une conversation au sein d’une application J2EE. Pour comprendre la notion de Conversation et l’interêt de Seam, je pense que le mieux
est de prendre un exemple. Prenons le cas de l’application de réservation de chambres d’hôtel de Seam.
L’achat doit se dérouler en 5 phases.
- Phase 1: l’utilisateur recherche un Hotêl dans une liste d’hôtels
- Phase 2: il examine un hôtel en particulier en le sélectionnant dans la liste. Il peut cliquer sur suivant/précedent pour passer d’un hôtel à l’autre.
- Phase 3: il clique sur « Réserver » ce qui l’amène à une page où il doit confirmer son achat, donner sa date d’arrivée et de départ. Il donne aussi son numéro de CB et d’autres détails
pour la facturation. Il termine par un click sur « Continue ». - Phase 4: Le moteur après avoir vérifié les disponibilités pour les dates choisies, propose de conclure la transaction via une action « Confirm ».
- Phase 5: le moteur enregistre la commande, il reçoit une facture détaillée qu’il peut imprimer, l’achat est validé et le panier est vidé.
Si vous avez déjà codé ce genre d’application, je pense que vous savez que les problèmes de gestion de session, d’unicité des transactions, de la taille de l’environnement dans l’objet HttpRequest sont parfois
voir souvent un problème. Sinon je vais expliquer ici les soucis que l’on rencontre souvent:
1) Le client ne termine pas la transaction
Pour une raison obscure (météorite, lassitude, plantage de Windows…) votre client ne termine pas sa transaction. Le souci est que pendant un delta T de temps sa session reste sur le serveur pour… rien !
2) Le navigateur du client n’accepte pas les cookies
Dans ce cas, la gestion de la session s’effectue en envoyant et en recevant via la session HTTP un ensemble de paramètres pour redéfinir à chaque appel le contexte d’execution, ce qui est lourd.
D’autre part il faut alors que la partie Vue de l’application s’occuppe de gérer ces sessions… ce qui complique et ralenti l’application.
3) Le client ouvre un autre navigateur et lance un autre achat
Il va alors falloir isoler les transactions du côté serveur ce qui est loin d’être le cas pour beaucoup de site Internet que je connais, spécialement ceux basés sur des moteurs PHP pour gérer un panier. Ils ne fonctionnent pas.
4) Le client clique sur le bouton Back de son navigateur
Combien d’application Web ne savent pas gérer le bouton Back et Forward ? Je me souviens d’une version du site www.sncf.fr qui m’a fait acheté 2 Paris-Bordeaux car j’avais utilisé le bouton Back et ensuite
Forward de mon navigateur… Inimaginable sur un système grand public.
JBoss Seam en s’attachant à gérer la notion de Conversation va nous permettre, à nous développeur Java, de conduire une transaction et un dialogue avec l’utilisateur très facilement. Je crois
qu’il s’agit réellement d’une nouveauté, qui fait que Seam est intéressant.
Voyons comment cela se présente:
Un des exemples de Seam est l’action « HotelBookingAction ».
Il s’agit d’un Session Stateful Bean comme le précise l’annotation @Stateful
Le tag @Name est un tag Seam pour donner un nom à ce SSB. Il servira dans la partie JSF pour déclencher les actions et les brancher sur des boutons dans la page Web.
Le tag @Interceptor est un tag Seam nécessaire pour réaliser la Bijection. J’en parlerai plus bas.
Le tag @Conversational est un marqueur pour dire que ce SSB doit s’executer dans un contexte de conversation.
Enfin le tag @LoggedIn est particulier à l’application HotelBooking. Il permet de dire que ce Bean ne doit s’executer que si l’utilisateur s’est préalablement authentifié. Pratique non?
@Stateful @Name("hotelBooking") @Interceptor(SeamInterceptor.class) @Conversational(ifNotBegunOutcome = "main") @LoggedIn public class HotelBookingAction implements HotelBooking, Serializable { private static final Logger log = Logger.getLogger(HotelBooking.class); @PersistenceContext(type = EXTENDED) private EntityManager em; private String searchString; @DataModel private List<Hotel> hotels; @DataModelSelectionIndex private int hotelIndex; @Out(required = false) private Hotel hotel; @In(required = false) @Out(required = false) @Valid private Booking booking; @In private User user; @In private transient FacesContext facesContext;
// Specify that this method starts a long running conversation @Begin public String find() { hotel = null; String searchPattern = searchString == null ? "%" : '%' + searchString.toLowerCase().replace('*', '%') + '%'; hotels = em.createQuery("from Hotel where lower(name) like :search or lower(city) like :search or lower(zip) like :search or lower(address) like :search") .setParameter("search", searchPattern) .setMaxResults(50) .getResultList(); log.info(hotels.size() + " hotels found"); return "main"; }
public String getSearchString() { return searchString; }
public void setSearchString(String searchString) { this.searchString = searchString; }
public String selectHotel() { if (hotels == null) return "main"; setHotel(); return "selected"; }
public String nextHotel() { if (hotelIndex < hotels.size() - 1) { ++hotelIndex; setHotel(); } return null; }
public String lastHotel() { if (hotelIndex > 0) { --hotelIndex; setHotel(); } return null; }
private void setHotel() { hotel = hotels.get(hotelIndex); log.info(hotelIndex + "=>" + hotel); }
public String bookHotel() { if (hotel == null) return "main"; booking = new Booking(hotel, user); booking.setCheckinDate(new Date()); booking.setCheckoutDate(new Date()); return "book"; }
@IfInvalid(outcome = REDISPLAY) public String setBookingDetails() { if (booking == null || hotel == null) return "main"; if (!booking.getCheckinDate().before(booking.getCheckoutDate())) { log.info("invalid booking dates"); FacesMessage facesMessage = new FacesMessage("Check out date must be later than check in date"); facesContext.addMessage(null, facesMessage); return null; } else { log.info("valid booking"); return "success"; } }
@End public String confirm() { if (booking == null || hotel == null) return "main"; em.persist(booking); log.info("booking confirmed"); return "confirmed"; }
@Destroy @Remove public void destroy() { log.info("destroyed"); } }
J’ai repris ici l’ensemble du code source de la démo Booking de Seam mais je vais expliquer le rôle des différentes annotations rencontrées pour vous présenter
un des principes de Seam, le contexte de conversation.
@Begin public String find() {
La méthode find est marqué avec l’annotation @Begin et la méthode confirm est marquée avec l’annotation @End. Ces 2 marqueurs permettent de commencer et de terminer la transaction.
Lorsque l’utilisateur déclenche l’action find pour lister les hotêls, la conversation commence. Il va pouvoir ensuite visualier un hotêl en détail, passer d’un hotêl à l’autre jusqu’à choisir
celui qui l’intéresse.
@End public String confirm() {
La method confirm() est appelé une fois qu’un hôtel a été sélectionné et qu’un objet Booking a été créé.
La méthode setBookingDetails est marquée avec l’annotation @IfInvalid(outcome = REDISPLAY) qui permet de dire que si dans la méthode lors de la spécification de la réservation il y a une erreur, alors la page courrant est réaffichée.
Qu’est-ce que la Bijection ?
Pour comprendre la bijection telle qu’elle est proposée par Seam, il faut connaître un moteur d’IoC (Inversion of Control) comme Spring. En quelques mots, l’inversion de contrôle est un pattern qui permet de déclarer des variables dans votre code, d’écrire une méthode setMyObject(Object newObject) mais de déléguer à un moteur l’assignation de cette variable. La Bijection c’est à la fois de l’inversion de contrôle mais aussi de la réinjéction de résultat dans le reste de l’application.
@In(required = false) @Out(required = false) @Validprivate Booking booking;
Le marqueur @In de Seam demande au moteur d’injecter la valeur de booking à l’execution.
Le marqueur @Out signifie que l’objet booking sera outjected vers une variable de contexte après que la méthode a été appelée. La variable booking qui est dans le contexte de l’utilisateur sera donc automatiquement remplacée par la valeur de cette variable après chaque appel. Cette notion d’outjection est spécifique à Seam et un peu complexe à expliquer ici.
Le marqueur @Valid vient d’Hibernate. Il permet de valider que l’objet est valide, que les attributs de cet objet obligatoire sont bien renseignés pour pouvoir le sauver.
Conclusion
JBoss Seam est un moteur basé sur les EJB3 et JSF qui vise à faciliter l’écriture d’une applicaiton. Je pense cependant qu’en attendant de bons tutoriaux ou livres sur le sujet, se lancer sur JBoss Seam pour un projet en production est encore trop tôt au moment où j’écris ces lignes (janvier 2006). Réellement novateur, Seam permettra dans quelques mois d’écrire des applications J2EE de nouvelles générations efficacement, facilement et rapidement. A suivre donc.