Play-framework ( 5 articles - Voir la liste )

Astuce [eZ4] Consommer des services web fournis par Play!

Voici une extension eZ Publish permettant de récupérer des webservices fournis par Play! : AT Play Connector.

Cette extension propose une application Play! d'exemple, correspondant aux exemples des articles précédents. Pour chaque service web de cette application, une méthode PHP et un fetch() permettent de récupérer son résultat côté eZ Publish.

Ces méthodes sont implémentées dans le fichier classes/ATWSFunctionCollection.php (à partir de la racine de l'extension), dont voici un extrait :

<?php

class ATWSFunctionCollection extends ATWebServices {

    /**
     * Appel du service web /test
     *
     * @param string $nom Nom des locataires recherchés
     * @param string $dateNaissance Date de naissance  des locataires recherchés
     * @param string $lang Langue des résultats
     */
    public static function test() {

        // Nom du service web
        $wsName = '/test';

        // Construction de la requête
        $wsRequest = $wsName;

        return array( 'result' => parent::getJSONResult( $wsRequest ) );
    }

    /**
     * Appel du service web /helloWorld
     *
     * @param string $name Nom de la personne à saluer
     */
    public static function helloWorld( $name ) {

        // Nom du service web
        $wsName = '/helloWorld';

        // Construction de la requête avec les paramètres
        $wsRequest = $wsName . '?';

        if ( $name != null ) {

            $wsRequest .= 'name=' . urlencode( $name );
        }

        return array( 'result' => parent::getJSONResult( $wsRequest ) );
    }
}

Explications :

  • Cette classe étend la classe ATWebservices également fournie par l'extension.
  • Chaque méthode ne fait que préparer le début d'une requête HTTP, avec le nom du service web et les paramètres à envoyer.
  • Ces requêtes HTTP sont complétées et soumises au services web de Play! via la méthode getJSONResult() de la classe ATWebServices.

Le fichier modules/atplayws/function_definition.php (à partir de la racine de l'extension) déclare des fetch() utilisables depuis les templates :

<?php

$FunctionList = array( );

$FunctionList['test'] = array(
        'name' => 'test',
        'operation_types' => array( 'read' ),
        'call_method' => array(
                'class' => 'ATWSFunctionCollection',
                'method' => 'test' ),
        'parameter_type' => 'standard',
        'parameters' => array( )
);

$FunctionList['hello_world'] = array(
        'name' => 'hello_world',
        'operation_types' => array( 'read' ),
        'call_method' => array(
                'class' => 'ATWSFunctionCollection',
                'method' => 'helloWorld' ),
        'parameter_type' => 'standard',
        'parameters' => array(
                array( 'name' => 'name',
                        'type' => 'string',
                        'required' => true ),
        )
);

Explications :

Pour chaque fetch, on déclare son nom, la méthode PHP correspondante à appeler, ainsi que les paramètres à lui fournir.

Remarque :

Pour plus de détails sur l'utilisation de l'extension, consultez le fichier README.txt présent à sa racine.

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.

Astuce Utiliser Play! Framework avec MySQL

Pour que Play! puisse se connecter à une base de données MySQL en Java, vous avez deux fichiers à modifier.

conf/application.conf

Voici un exemple de configuration avec une base de données MySQL de base dans WAMPServer.

# Database configuration
# ~~~~~ 
# You can declare as many datasources as you want.
# By convention, the default datasource is named `default`
#
# db.default.driver=org.h2.Driver
# db.default.url="jdbc:h2:mem:play"
# db.default.user=sa
# db.default.password=
#
# You can expose this datasource via JNDI if needed (Useful for JPA)
# db.default.jndiName=DefaultDS

db.default.driver=com.mysql.jdbc.Driver
db.default.url="jdbc:mysql://localhost:3306/database_name" 
db.default.user="root" 
db.default.password=""

Les 4 dernières lignes permettent de choisir :

  • le driver Java à utiliser,
  • l'adresse, le port et le nom de la base de données à laquelle se connecter
  • le login et le mot de passe de l'utilisateur à utiliser

project/Build.scala

import sbt._
import Keys._
import PlayProject._

object ApplicationBuild extends Build {

    val appName         = "MyApplication"
    val appVersion      = "1.0-SNAPSHOT"

    val appDependencies = Seq(
     // Dépendances du projet :
     "mysql" % "mysql-connector-java" % "5.1.20"
    )

    val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA).settings(
      // Configuration du projet :     
    )

}

La ligne à ajouter est : "mysql" % "mysql-connector-java" % "5.1.20"

Pour que Play! prenne en compte ces modifications (en particulier la modification de Build.scala), vous devez relancer Play! et lui demander de recharger la configuration :

cd path/to/my/play/application
play
$ reload
$ update
$ run

Astuce Mise en place de Play!

Installation

Tout d'abord, vous avez besoin d'un JDK 6 ou supérieur pour pouvoir utiliser Play! en Java.

  • Téléchargez la dernière version de Play! Framework.
  • Extrayez l'archive où vous le souhaitez. (Par exemple D:\dev\play-2.0.3)
  • Ajoutez le dossier racine de Play! à votre PATH système. Si vous êtes sou Linux, utilisez cette commande export PATH=$PATH:/path/to/play-2.0.3. Si vous êtes sous Windows, modifiez les variables d'environnement.
  • Redémarrez votre ordinateur
  • Vérifiez l'installation. La commande suivante ne doit pas retourner d'erreur :
play help

Création d'une application avec Play!

Si vous utilisez eclipse, Netbeans, ou un autre IDE pour Java possédant un workspace, il est préférable de créer votre application dans ce workspace.

Play! peut créer l'application pour vous, via la commande :

play new chemin/vers/le/workspace/nom_application
  • Choisissez le nom de votre application (Par exemple PlayWS)
  • Choisissez simple Java application

Console nouvelle appli Play!

  • Play! a maintenant créé la base de fichiers nécessaires à votre application.

Utiliser Play! dans eclipse

Pour utiliser Play! avec eclipse, il faut "eclipsifier" votre application. Pour cela, utilisez les commandes suivantes :

  • Rendez-vous dans le dossier contenant votre nouvelle application et lancez Play! :
cd chemin/vers/le/workspace/nom_application
play
  • Dans l'invite de commande de Play!, appelez la commande eclipsify :
$ eclipsify
  • Lancez eclipse, choisissez "Importer" > "Importer un projet existant dans le workspace".
  • Sélectionnez le dossier racine de l'application générée par Play! et validez.

Remarque : Si vous obtenez une erreur pendant la commande eclipsify ressemblant à class file has wrong version 50.0, should be 49.0, c'est que vous devez mettre à jour votre JDK ou modifier vos variables d'environnement.

Astuce [eZ4] Utiliser eZ Publish avec Play! Framework

Play! Framework

Play! est un framework simple de développement MVC web open-source basé sur Java.

Ce n’est pas un framework Java EE puisqu’il n’implémente pas la norme servlet. Il n’a donc pas besoin de serveur d’application comme Tomcat ou WebSphere et utilise le « serveur http » JBoss Netty permettant de meilleures performances.

Il utilise une architecture stateless ce qui rend son utilisation idéale pour le développement de services web REST.

Pourquoi utiliser Play! ?

Vous pouvez faire à peu près autant de chose avec PHP qu'avec Java, mais si la solution dont vous avez besoin est déjà existante en Java, pourquoi la recréer en PHP ?

Play! étant basé sur Java, vous pouvez l'utiliser comme interface entre une solution Java et votre projet eZ Publish.

L'architecture eZ Publish + Play!

Architecture eZ Publish - Play

EZ Publish communique avec Play! en lui soumettant des requêtes REST via HTTP. Play! lui retourne en réponse les données au format JSON (par défaut). Play! fournit donc des services web utilisables par eZ Publish.