Introduction▲
Malgré son succès pour le déploiement d'applications web côté serveur, Java souffre d'une réputation de technologie compliquée et difficile à mettre en œuvre. Cette constatation est parfaitement vraie dans le cas de Java Enterprise Edition, conçue pour soutenir de très grosses applications réparties sur plusieurs machines. La plateforme Java SE suffit cependant pour construire des applications web grâce aux servlets, aux Java Server Pages (JSP) et à un simple serveur comme Tomcat, également appelé conteneur de servlets. Les JSP et servlets ne constituent que la couche de plus bas niveau des applications web écrites en Java. Pour simplifier et accélérer les développements, de nombreux frameworks sont disponibles, proposant tous des fonctionnalités et des concepts très différents.
Certains de ces frameworks, comme Spring, Turbine, Tapestry, Cocoon ou encore le fameux Struts, sont largement répandus et utilisés par de nombreuses entreprises. Bien que Wicket ne soit à première vue qu'un framework de plus dans cet univers déjà surchargé, les motivations de ses auteurs sont très claires. Contrairement aux offres existantes, Wicket est simple à apprendre, ne nécessite pas de code HTML spécifique ni de fichier de configuration XML compliqué et repose sur une API semblable à Swing. À l'instar des Java Server Faces (JSF) ou d'ASP.NET, Wicket maintient en outre automatiquement l'état des composants sur le serveur. Un champ texte d'une page HTML est par exemple lié à un composant et à un modèle Java du côté serveur. Cette solution permet de vous affranchir d'une grande partie de la gestion des sessions utilisateur. Enfin, comme nous allons le voir, Wicket rend très facile la séparation entre la logique et la présentation.
Mise en route▲
Le framework Wicket existe aujourd'hui en versions 1.0 et 1.1-beta2. Nous allons utiliser cette dernière pour nos premiers pas. Vous trouverez cet exemple dans l'application login-wicket (HTTP - FTP). La distribution binaire contient une bibliothèque appelée wicket-1.1-b2.jar que vous devrez copier dans le dossier WEB-INF/lib de votre application web. Vous devrez également copier les dépendances qui se trouvent dans le répertoire lib/ de Wicket. Il ne vous reste plus qu'à créer le descripteur web.xml de l'application comme celui présenté dans le listing 1.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd"
>
<web-app>
<display-name>
Login: Wicket</display-name>
<servlet>
<servlet-name>
HelloWorldApplication</servlet-name>
<servlet-class>
wicket.protocol.http.WicketServlet</servlet-class>
<init-param>
<param-name>
applicationClassName</param-name>
<param-value>
com.loginmag.HelloWorldApplication</param-value>
</init-param>
<load-on-startup>
1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>
HelloWorldApplication</servlet-name>
<url-pattern>
/helloworld/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>
ChatApplication</servlet-name>
<servlet-class>
wicket.protocol.http.WicketServlet</servlet-class>
<init-param>
<param-name>
applicationClassName</param-name>
<param-value>
com.loginmag.ChatApplication</param-value>
</init-param>
<load-on-startup>
1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>
ChatApplication</servlet-name>
<url-pattern>
/chat/*</url-pattern>
</servlet-mapping>
</web-app>
Ce descripteur peut prêter à confusion, car il est possible de créer plusieurs applications Wicket dans une même application web. Pour créer une nouvelle application Wicket, il vous suffit d'utiliser le servlet WicketServlet et d'utiliser le paramètre applicationClassName pour indiquer la classe représentant l'application à proprement parler. Une application est une classe Java héritant wicket.protocol.http.WebApplication et servant de point d'entrée et de configuration. La méthode getPages() renvoie la propriété la plus importante de votre application, une instance d'ApplicationPages qui vous permet de définir la page d'accueil et les pages d'erreur. Le listing 2 présente l'implémentation minimale d'une application Wicket.
package
com.loginmag;
import
wicket.protocol.http.WebApplication;
public
class
HelloWorldApplication extends
WebApplication {
public
HelloWorldApplication
(
) {
getPages
(
).setHomePage
(
HelloWorldPage.class
);
}
}
La page d'accueil est passée en paramètre de la méthode setHomePage() sous la forme d'une classe Java (une instance de Class), dans notre cas HelloWorldPage.class. Une fois la page d'accueil spécifiée, toute requête sur l'URL http://localhost:8080/login-wicket/helloworld renverra le code HTML généré par la page HelloWorldPage.
Une page Wicket est constituée de deux éléments : une classe Java héritant wicket.markup.html.WebPage et un fichier HTML portant le même nom que la classe, HelloWorldPage.html dans notre cas. Le listing 3 présente le contenu de la page HTML.
<html>
<head>
<title>
Login: Wicket</title>
</head>
<body>
<span
wicket
:
id
=
"message"
>
Emplacement du message</span>
</body>
</html>
Le code n'utilise que des balises HTML ou XHTML classiques. La seule différence réside dans l'utilisation du namespace wicket pour identifier les composants pour le code Java. Dans cet exemple nous avons créé un élément <span /> appelé message et contenant un texte par défaut.
Cette approche, très simple, permet aux designers de concevoir l'apparence visuelle du site sans se soucier d'intégrer des instructions de programmation. Ils pourront ainsi éditer les pages dans un outil comme DreamWeaver ou Nvu, à condition de conserver les wicket:id des composants. Lors de la conception visuelle des pages, les designers utiliseront les valeurs par défaut, comme celles de notre exemple. De la même manière, les développeurs n'auront pas à s'inquiéter d'interférer avec l'apparence du site et pourront se concentrer sur l'implémentation de la logique. Pour vous en convaincre, étudiez le code Java de HelloWorldPage présenté dans le listing 4.
package
com.loginmag;
import
wicket.markup.html.WebPage;
import
wicket.markup.html.basic.Label;
public
class
HelloWorldPage extends
WebPage {
public
HelloWorldPage
(
) {
add
(
new
Label
(
"message"
, "Hello World !"
));
}
}
L'unique ligne de cette page ajoute un composant de type Label pour lequel le premier paramètre définit le nom et le second paramètre la valeur. Lorsqu'un visiteur arrivera sur votre page, Wicket exécutera votre code pour remplacer le contenu de la balise <span /> par la valeur du Label.
Utilisation des formulaires▲
Notre second exemple est un petit chat que vous trouverez dans l'application Wicket ChatApplication. Il ne contient qu'une seule page, ChatPage, qui permet aux visiteurs d'envoyer des messages. Le listing 5 contient une version simplifiée de la page HTML représentant le chat.
<form
wicket
:
id
=
"chatForm"
>
Surnom : <input
type
=
"text"
wicket
:
id
=
"nick"
></input><br />
Message : <input
type
=
"text"
wicket
:
id
=
"message"
></input><br />
<input
type
=
"submit"
value
=
"Envoyer"
/>
</form>
<span
wicket
:
id
=
"messages"
>
<span
wicket
:
id
=
"nick"
>
Invité</span>
: <span
wicket
:
id
=
"message"
>
Message.</span><br /><br />
</span>
Dans cette page se trouvent deux composants racines, un formulaire chatForm et une liste de messages appelée messages. Ils contiennent chacun un champ nick et un champ message pour permettre au visiteur de saisir ou de voir les messages. En aparté, cet exemple montre que Wicket gère parfaitement des arbres de composants. Nous avons appris au début de cet article que les composants HTML sont liés à un composant Java et à un modèle. Dans cet exemple, le formulaire permet de remplir les informations d'un modèle qui seront lues pour l'affichage des messages. À l'opposé de Java EE et de ses EJB, Wicket se contente de POJO (Plain Old Java Object) pour gérer les modèles. Le modèle du chat correspond à la classe Message qui se trouve sur le CD-Rom.
Elle implémente simplement l'interface Serializable pour permettre à Wicket de la sauvegarder en session, et expose ses attributs avec des accesseurs et mutateurs. Conformément à la page HTML, un message contient le surnom de l'utilisateur, attribut nick, et son message, attribut message. La réalisation de la page Java responsable de la liaison entre la page HTML et le modèle demande d'utiliser de nouveaux composants Wicket comme le montre le listing 6 qui contient la définition de la page ChatPage.
public
class
ChatPage extends
WebPage {
private
static
List messagesList =
new
ArrayList
(
);
private
ListView messagesListView;
public
ChatPage
(
) {
add
(
new
ChatForm
(
"chatForm"
));
add
(
messagesListView =
new
ChatListView
(
"messages"
, messagesList));
}
// ...
}
Le premier composant personnalisé est ChatForm, dérivant de Form, capable de gérer les champs du formulaire. Le second composant est un dérivé de ListView, ChatListView, que nous utilisons pour afficher la liste des messages saisis par les utilisateurs. Le modèle de cette liste est une ArrayList statique afin qu'elle soit visible de tous les visiteurs du site. Lors du rendu de la page, la méthode populateListItem() du composant ChatListView sera invoquée pour chaque élément de la liste messagesList. Le listing 7 présente le code utilisé pour modifier les valeurs des composants HTML correspondant à chaque élément.
private
class
ChatListView extends
ListView {
// ...
public
void
populateItem
(
ListItem listItem) {
Message message =
(
Message) listItem.getModelObject
(
);
listItem.add
(
new
Label
(
"nick"
, message.getNick
(
)));
listItem.add
(
new
Label
(
"message"
, message.getMessage
(
)));
}
}
Le composant ListItem passé en paramètre est le conteneur dans lequel se trouvent les composants nick et message. Il suffit donc de récupérer le modèle à l'aide de getModelObject() et d'invoquer ses accesseurs pour obtenir les valeurs désirées.
Le modèle que nous venons d'extraire est ajouté à la liste lors de la soumission du formulaire. Ce dernier est un peu plus complexe à mettre en place, car il doit être lié à l'objet du modèle comme le montre le listing 8.
public
ChatForm
(
final
String componentName) {
super
(
componentName, new
CompoundPropertyModel
(
new
Message
(
)), null
);
add
(
new
TextField
(
"nick"
));
add
(
new
TextField
(
"message"
));
}
Lors de l'appel du constructeur parent de la classe Form nous passons en paramètre le nom du composant ainsi qu'un IModel. La documentation de wicket.model.IModel présente les différents types de modèles disponibles, mais nous devons ici utiliser un CompoundModel pour le partager en deux composants, le formulaire et la liste. Le CompoundModel prend lui-même en paramètre notre objet modèle. Tous les composants ajoutés au formulaire seront automatiquement liés à l'attribut du modèle possédant le même nom (ainsi le champ nick sera lié à getNick et setNick). Enfin, lorsque le visiteur clique sur le bouton Envoyer du formulaire, la méthode onSubmit() est exécutée.
public
final
void
onSubmit
(
) {
Message message =
(
Message) getModelObject
(
);
Message newMessage =
new
Message
(
message);
messagesListView.modelChanging
(
);
messagesList.add
(
0
, newMessage);
messagesListView.modelChanged
(
);
message.setMessage
(
""
)
}
Souvenez-vous que Wicket conserve l'état des composants, et donc leurs modèles, en mémoire. Vous devez donc dupliquer l'objet modèle pour ne pas modifier les valeurs de la liste lors du prochain affichage du formulaire. Cela fait, il ne vous reste qu'à ajouter le message dans la liste messagesList. Les deux appels modelChanging() et modelChanged() permettent de protéger les accès concurrents (souvenez-vous qu'un serveur web est multithread). La dernière ligne efface le contenu du champ Message lorsque la page est rechargée. Vous constaterez que le contenu du champ Surnom contient toujours ce que l'utilisateur a saisi, car Wicket a conservé le même modèle en mémoire. Ces exemples devraient vous convaincre de la simplicité et de l'élégance de l'architecture de Wicket. Vous pouvez vous rendre sur le site http://wicket.sf.net pour consulter les exemples et la documentation.
Romain Guy