lundi 10 août 2009

Wicket : gestion des erreurs

Mise à jour : il existe une manière plus simple de faire ce qui est décrit ci-dessous. Je mettrai le billet à jour des que j'aurai un moment. N'hésitez pas à me pinger par commentaire si vous avez un besoin urgent à ce sujet.


Ce soir nous allons voir comment personnaliser la gestion des exceptions avec Wicket.
Par défaut, quand une exception est remontée à la couche IHM, Wicket affiche une jolie "Internal error". A partir de là, impossible de continuer sa navigation et l'utilisateur n'a aucune information sur la nature de l'erreur rencontrée.
Heureusement, il existe un moyen de personnaliser le comportement de Wicket lors d'une levée d'exception.

En premier lieu, nous allons créer notre classe d'exceptions.
La classe MyException hérite du type Exception, et y ajoute la gestion de messages internationalisables :


public class MyException extends Exception {

private String key;

public MyException(String key, Throwable cause) {
super(cause);
this.key=key;


Dans le code métier, il suffira de lever les exceptions de cette façon pour y ajouter un message personnalisé :

try {
//mon code
}

catch(UneException e){

throw new MyException("my.message.key", e);
}


Attention, la technique suivante suppose que l'on utilise le constructeur WebPage(PageParameters parameters)pour construire les pages nécessitant des paramètres d'entrée (par exemple l'identifiant de la personne que l'on édite dans une application de gestion de contacts).

Dans chaque page succeptible d'afficher un message d'erreur, on ajoute un FeedbackPanel et on traite le message :

public MyPage(PageParameters parameters) {
super(parameters);
[…]

FeedbackPanel  feedback = new FeedbackPanel("feedback");
if (getPageParameters().getString("key") != null){
feedback.error(getString(getPageParameters().getString("key")));
}
}

On définit le message que l'on veut afficher pour cette clé d'erreur dans un fichier de propriété d'internationalisation.


Configuration d'un handler

L'étape suivante consiste à créer un handler pour wicket qui va attraper les exceptions et les traiter comme on en a envie.

Dans la classe principale de votre application (celle qui étend WebApplication) , ajoutez la méthode suivante :


@Override
public final RequestCycle newRequestCycle(final Request request, final Response response) {
return new ExecutionHandlerRequestCycle (this, (WebRequest)request, (WebResponse)response);
}


Il ne reste plus qu'à créer le fameux handler!

public class ExecutionHandlerRequestCycle extends WebRequestCycle {
public ExecutionHandlerRequestCycle(WebApplication application,
WebRequest request, Response response) {
super(application, request, response);
}

@Override
public Page onRuntimeException(Page page, RuntimeException e) {
LoggerFactory.getLogger(ExecutionHandlerRequestCycle.class).debug(e.getMessage());
Throwable ex = null;
if (e.getCause() instanceof InvocationTargetException) {
ex = ((InvocationTargetException)e.getCause()).getTargetException();
}
else{
ex = e;
}
if (ex instanceof MyException) {
PageParameters params = page.getPageParameters();
if(params != null){
params.remove("key");
params.add("key", ((MyException)ex).getKey());
}
Class pageClass = page.getClass();
Page newPage = null;
try {
Constructor construct = pageClass.getConstructor(new Class [] {Class.forName ("org.apache.wicket.PageParameters")});
newPage = (Page)construct.newInstance(new Object [] {params});
} catch (Exception e1) {
try {
newPage = page.getClass().newInstance();
} catch (Exception e2) {
LoggerFactory.getLogger(ExecutionHandlerRequestCycle.class).error(e2.getMessage());
return new GenericExceptionPage();
}
}
return newPage;
}
else {
return new GenericExceptionPage();
}
}
Tout ceci nécessite quelques explications : premièrement on attrape l'exception et on vérifie qu'elle n'est pas encapsulée dans une InvocationTargetException , ce qui arrive par exemple lorsque l'on utilise Spring entre sa couche wicket et les couches plus basses.
On récupère également l'objet page qui correspond à la page sur laquelle l'utilisateur se trouvait avant que l'erreur ne survienne ainsi que les paramètres associés à la requête qui a conduit à cette page. Avec ces éléments en main, nous sommes capables de ramener l'utilisateur sur la page où il se trouvait précédemment, en réinjectant ces paramètres dans la reqûete HTTP.
On récupère alors le code d'erreur, on le rajoute aux paramètres de la page, reconstruite grâce à l'api de reflexion Java.
Si on a ajouté un FeedbackPanel à notre page comme nous l'avons vu plus haut, nous verrons le message s'afficher dans la page sans perdre notre navigation dans l'application!

Comme vous l'avez deviné en lisant le code, on crée également une page pour les exceptions non gérées que l'on va appeler GenericExceptionPage, où l'on peut afficher par exemple le message d'erreur suivant : "Une erreur technique est survenue, veuillez contacter votre administrateur...". Il est également possible d'afficher dans cette page un lien vers la page d'accueil de l'application ou encore un menu de navigation... . C'est cette page qui sera appelée quand une exception que l'on aura pas levée explicitement avec le type MyException sera attrapée par Wicket.