Dans les deux derniers articles, on a vu les différentes variantes de la méthode render(), permettant de répondre du JSON ou du contenu HTML avec les templates par exemple:
Play – Controller et les variantes de render() (JSON, XML, fichier, …)
Sur des exemples simples, cela semble suffisant mais chaque « action » d’un Controller est un point d’entrée de votre application. Pour gérer les traitements en commun, vous devriez ajouter du code dans chacun de vos Controller, une répétition de code qui n’est jamais bonne à écrire.
Prenez par exemple la vérification des droits utilisateurs sur une partie de votre site. Pour toutes les actions, vous devrez vérifier d’abord si l’utilisateur est bien administrateur et si non, le renvoyer sur la page de login.
Au lieu de devoir répéter ces codes, on va utiliser des « Interceptors » dans le jargon de Play. Ces méthodes font partie de votre vos Controller mais on peut aussi les modulariser en les isolant dans une classe à part. Les Interceptors sont « static » mais pas « public », contrairement aux actions. Des annotations Java permettent de leur donner un comportement précis.
Si je ne trompe pas, cela ressemble beaucoup à de la programmation orienté aspect.
Interceptor @Before
On va reprendre l’exemple de l’espace administrateur pour lequel on devra vérifier pour toutes les actions que l’utilisateur est bien connecté. Dans cet exemple, on ajoute une méthode checkAuthentification() avec l’annotation @Before:
public class Admin extends Controller { @Before static void checkAuthentification() { if(session.get("user") == null) login(); } public static void index() { List<User> users = User.findAll(); render(users); } … }
A noter que notre Interceptor @Before sera appelé pour toutes les actions de ce Controller. Si vous voulez modifier cela, vous pouvez donner une liste d’action à exclure:
@Before(unless="login") static void checkAuthentification() { if(session.get("user") == null) login(); }
Ou une liste exclusive avec « only »:
public class Admin extends Controller { @Before(only={"login","logout"}) static void doSomething() { … } … }
Ces modifiers only et unless sont utilisables avec @Before, @After et @Finally
Interceptor @After
Même combat mais effectué après l’appel à une action du Controller. Pratique pour le log par exemple:
public class Admin extends Controller { @After static void log() { Logger.info("Action executed ..."); } public static void index() { List<User> users = User.findAll(); render(users); } … }
Interceptor @Catch
Permet de récupérer les exceptions qui peuvent être lancées par les actions du Controller de manière globale. L’exception est passée en paramètre de la méthode annotée @Catch:
public class Admin extends Controller { @Catch(IllegalStateException.class) public static void logIllegalState(Throwable throwable) { Logger.error("Illegal state %s…", throwable); } public static void index() { List<User> users = User.findAll(); if (users.size() == 0) { throw new IllegalStateException("Invalid database - 0 users"); } render(users); } }
Notez que vous pouvez aussi réaliser un filtrage plus fin sur les exceptions à @Catch (voir documentation Play).
Interceptor @Finally
L’interceptor @Finally ressemble beaucoup à @After sauf qu’il sera appelé dans tous les cas, même si une erreur se produit pendant l’exécution de votre code (comme un try/catch/finally):
public class Admin extends Controller { @Finally static void log() { Logger.info("Response contains : " + response.out); } public static void index() { List<User> users = User.findAll(); render(users); } … }
L’annotation @With pour ajouter plus d’Interceptor
Pour l’instant, on a uniquement vu les Interceptor qui résidaient au sein de la classe de Controller. Pas terrible par exemple pour votre vérification administrateur que vous devrez faire dans plusieurs Controller. Heureusement, Play vous permet de décentraliser ce code dans un Controller dédié. Par exemple, on crée un Controller « Secure.java »:
public class Secure extends Controller { @Before static void checkAuthenticated() { if(!session.containsKey("user")) { unAuthorized(); } } }
Que l’on peut ensuite utiliser dans un autre Controller en ajoutant l’annotation @With sur le/les Controller à impacter:
@With(Secure.class) public class Admin extends Controller { … }
Cela vous évite de vous répéter et vous permet de laisser vos Controllers « propres », avec uniquement la Business Logic.