Astuce Créer des services web avec Play!

Tracer sa route

Play! vous permet de créer des services web accessibles via de simples requêtes HTTP.

Vous devez pour cela définir des routes, pour que l'application sache quelle méthode exécuter en fonction de la requête HTTP.

C'est le rôle du fichier conf/route, dont voici un exemple :

# Routes
# Ce fichier définit les différentes routes (avec les routes prioritaires en premier)
# ~~~~

# Mappe les fichiers de ressources statiques du répertoire /public avec le chemin /assets dans l'URL
GET     /assets/*file          controllers.Assets.at(path="/public", file)

GET     /test                  controllers.Application.test()
GET     /helloYou              controllers.Application.helloYou(firstName, lastName)
GET     /userList              controllers.Application.userList()

Explications :

  • Pour chaque service web, on définit une route de la forme : <Type de requête (GET|POST)> <url> <package.Classe.method()>.

  • L'appel de l'URL http://localhost:9000/test retournera le résultat de la méthode test() de la classe Application du package controllers.

  • La méthode helloYou() nécessite les arguments firstName et lastName.
    Pour l'appeler, l'url sera http://localhost:9000/helloYou?firstName=Jean-Louis&lastName=David.
    Comme la requête est définie en GET, il suffit d'ajouter les paramètres dans l'url sous la forme nom_param=valeur.

Remarque :

La première route (GET /assets/*file...) est un peu particulière et sert au fonctionnement interne de Play!, pour gérer les fichiers statiques de l'application.

Une méthode qui a la classe (et vice-versa)

Voici un aperçu de la classe Java qui contient les méthodes définie dans le fichier route :

package controllers;

import java.util.Date;

import models.WSResult;
import models.beans.Bean;
import models.beans.MessageBean;
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Result;

public class Application extends Controller {

    public static Result test() {

        WSResult result = new WSResult();

        result.addBean(new Bean() {
            public String message = "Test"; 
            public Date date = new Date(); 
        });

        return ok(Json.toJson(result));
    }

    public static Result helloYou(String firstName, String lastName) {

        WSResult result;

        if (!firstName.equals("") && !lastName.equals("")) {
            result = new WSResult();
            result.addBean(new MessageBean("Hello " + firstName + " " + lastName + "!"));
        } else {
            result = new WSResult("Paramètres incorrectes.", WSResult.ERROR_CODE_CUSTOM_PARAMS);
        }

        return ok(Json.toJson(result));
    }
}

Explications :

  • La classe Application étend la classe Controller fournie par Play!.
  • Pour chaque URL définie dans le fichier route, on retrouve bien la méthode dans la classe Application, avec ses arguments. (Notez que les noms des arguments doivent être identiques.)
  • Chaque méthode est statique et retourne un objet Result (classe également fournie par Play!).
  • Dans cet exemple, à chaque Result est associée une liste d'objets Bean.
  • Un objet Bean regroupe des informations à rendre accessible via un service web (un message, le résultat d'un requête SQL, ...)
  • Dans cet exemple, la classe Result a été étendue par la classe WSResult, pour lui ajouter une liste d'objets Bean, un éventuel message d'erreur, et d'autres informations.
  • Chaque méthode retourne un objet Result en JSON, via la méthode ok(Json.toJson(result)) fournie par Play!.
  • La seconde méthode vérifie si les paramètres sont vides. Si oui, alors l'objet Result ne contiendra pas de Bean mais un message et un code d'erreur. Si non, il contiendra un Bean comme dans la première méthode.

Un vrai Bean, monsieur

Normalement, un Bean représente une ligne de données provenant d'une source (base de données, fichier, ...). C'est une classe Java des plus simples, avec juste des attributs et leurs getter() et setter().

Imaginons par exemple qu'on veuille représenter un utilisateur de site web présent dans une table de base de données, on pourrait avoir :

package models.beans;

public class UserBean extends Bean {

    private int userID;
    private String email;
    private String login;
    private String password;

    public static final String FIELD_USER_ID  = "user_id";
    public static final String FIELD_EMAIL    = "email";
    public static final String FIELD_LOGIN    = "login";
    public static final String FIELD_PASSWORD = "password";

    public int getUserID() {
        return userID;
    }

    public void setUserID(int userID) {
        this.userID= userID;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getLogin() {
        return login;
    }

    public void setLogin(String login) {
        this.login = login;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

Explications :

  • On étend la classe Bean, car la classe WSResult attend des objets Bean dans sa liste.
  • En plus des attributs caractérisant un utilisateur, on ajoute des attributs publics et statiques pour préciser quels champs de la base de données correspondent.
  • Pour que Play! puisse se connecter à une base de données, vous devez procéder à quelques configurations.

Retour à la base

Une fois que la classe UserBean a été créée, il faut écrire dans la classe Application la méthode qui va consulter la base de données et retourner un objet Result :

package controllers;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import models.WSResult;
import models.beans.UserBean;
import play.libs.Json;
import play.mvc.Result;

public class Application extends ApplicationController {

    /**
     * Retourne la liste des utilisateurs de la base de données.
     * 
     * @return Result
     */
    public static Result userList() {

        WSResult result  = new WSResult();
        String queryName = "user_list";

        String query = ApplicationController.getQuery(queryName);

        PreparedStatement statement;
        try {

            statement = ApplicationController.getConnection().prepareStatement(query);
            ResultSet resultSet = statement.executeQuery();

            // Parcours des lignes retournées
            while (resultSet.next()) {

                UserBean bean = new UserBean();
                bean.setId(resultSet.getInt(UserBean.FIELD_ID));
                bean.setEmail(resultSet.getString(UserBean.FIELD_EMAIL));
                bean.setLogin(resultSet.getString(UserBean.FIELD_LOGIN));
                bean.setPassword(resultSet.getString(UserBean.FIELD_PASSWORD));

                result.addBean(bean);
            }

        } catch (SQLException e) {

            result = new WSResult("Erreur SQL.", WSResult.ERROR_CODE_SQL);
            result.setErrorCode(e.getErrorCode());
            result.setErrorMessage(e.getMessage());
        }

        return ok(Json.toJson(result));
    }
}

Explications :

  • La méthode utilise l'api java sql, pour préparer les requêtes et les exécuter.
  • On parcourt ensuite les lignes retournées par la source de données
  • Pour chaque ligne de résultat, on instancie un nouveau Bean (UserBean ici puisqu'il s'agit d'utilisateurs) et on l'ajoute à la liste de Beans de l'objet Result.
  • On retourne l'objet Result transformé en JSON.
  • En cas d'erreur SQL, on retourne un objet Result contenant le message et le code d'erreur SQL.

Stockage des requêtes

Dans la méthode ci-dessus, on ne voit aucun code SQL. Les requêtes sont stockées dans un fichier qui les regroupe toutes : conf/sql.properties.

Comme tout fichier .properties standard, chaque ligne est de la forme clé = valeur :

user_list = SELECT * FROM user
admin_user = SELECT * FROM user WHERE login = 'admin'

Ces requêtes sont récupérées dans les méthodes java à partir de leurs clés :

String queryName = "user_list";
String query = ApplicationController.getQuery(queryName);

Lancement des requêtes

Finalement, si vous tapez http://localhost:9000/userList dans votre navigateur, vous obtenez quelque chose de la forme :

Résultat JSON user list

En résumé :

  • Play! reçoit votre requête HTTP.
  • Il cherche dans son fichier route la méthode correspondante et l'appelle.
  • La méthode cherche la requête SQL demandée, l'exécute et parcours les résultats.
  • Ces résultats sont "transformés en Bean" et stockés dans un objet Result.
  • L'objet Result est transformé en JSON et Play! vous en retourne le flux.

Remarque : Si vous avez un affichage du JSON illisible, il existe une extension Firefox très utile.