Imaginez le Diagramme UML suivant :

Qui pourrait parfaitement représenter les Objets d’un Blog, mais pas que.

Imaginez que vous souhaitiez réaliser un API, compatible REST, qui vous permette de récupérer vos Posts et Comments au format JSon.

Nos Objets Métiers

Commençons tout d’abord par écrire nos objets Model :
app/models/Post.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package models;
 
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import play.data.validation.Required;
import play.db.jpa.Model;
 
@Entity
public class Post extends Model {
    @Required
    public String author;
 
    @Required
    public String title;
 
    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
    public List<Comment> comments;
}

app/models/Comment.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package models;
 
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import play.data.validation.MaxSize;
import play.data.validation.Required;
import play.db.jpa.Model;
 
@Entity
public class Comment extends Model {
    @Required
    public String author;
 
    @Lob
    @Required
    @MaxSize(10000)
    public String content;
 
    @ManyToOne
    @Required
    public Post post;
}

Données de référence & Configuration

Pour nous simplifier la vie, Play! prévoit un mécanisme très pratique d’import de données, basé sur l’utilisation d’un fichier au format YAML
conf/initial-data.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Post(post1):
  author: moi
  title: Post 1
 
Post(post2):
  author: moi
  title: Post 2
 
Comment(c1):
  author: moi
  content: du Contenu
  post: post1
 
Comment(c2):
  author: moi
  content: du Contenu
  post: post1
 
Comment(c3):
  author: moi
  content: du Contenu
  post: post2

conf/application.conf

1
2
3
...
db=mem
...

Bootstrap Job & Controller

Le Bootstrap Job va nous permettre d’importer nos données de référence sans effort, et notre contrôleur devra réaliser notre besoin initial, à savoir nous retourner la liste des Posts
app/controllers/Bootstrap.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package controllers;
 
import models.Post;
import play.jobs.Job;
import play.jobs.OnApplicationStart;
import play.test.Fixtures;
 
@OnApplicationStart
public class Bootstrap extends Job {
    public void doJob() {
        if (Post.count() == 0) {
            Fixtures.loadModels("initial-data.yml");
        }
    }
}

app/controllers/Application.java

1
2
3
4
5
6
7
8
9
10
11
12
package controllers;
 
import java.util.List;
import models.Post;
import play.mvc.Controller;
 
public class Application extends Controller {
    public static void index() {
        List<Post> posts = Post.findAll();
        renderJSON(posts);
    }
}

Premier résultat

Après avoir accédé via votre navigateur à l’adresse http://localhost:9000, vous devriez avoir l’erreur suivante :

Et dans votre console :

@67jf5njpa
Internal Server Error (500) for request GET /

Execution exception (In /app/controllers/Application.java around line 12)
IllegalStateException occured : circular reference error   Offending field: post    Offending object: preserveType: false, type: class models.Post, obj: Post[1]

Corrections

La méthode renderJSON prévoit l’utilisation de vos propre JSonSerializer. Ce que nous allons faire pour nos Objets Post et Comment.

app/models/serializer/PostJSonSerializer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package models.serializer;
 
import java.lang.reflect.Type;
 
import models.Post;
 
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
 
public class PostJSonSerializer implements JsonSerializer<Post> {
    public static PostJSonSerializer instance;
 
    private PostJSonSerializer() {
    }
 
    public static PostJSonSerializer get() {
        if (instance == null) {
            instance = new PostJSonSerializer();
        }
        return instance;
    }
 
    public JsonElement serialize(Post post, Type type, JsonSerializationContext jsonSerializationContext) {
        JsonObject obj = new JsonObject();
        obj.addProperty("author", post.author);
        obj.addProperty("title", post.title);
        obj.add("comments", jsonSerializationContext.serialize(post.comments));
        return obj;
    }
}

app/models/serializer/CommentJSonSerializer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package models.serializer;
 
import java.lang.reflect.Type;
import models.Comment;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
 
public class CommentJSonSerializer implements JsonSerializer<Comment> {
    public static CommentJSonSerializer instance;
 
    private CommentJSonSerializer() {
    }
 
    public static CommentJSonSerializer get() {
        if (instance == null) {
            instance = new CommentJSonSerializer();
        }
        return instance;
    }
 
    public JsonElement serialize(Comment comment, Type type, JsonSerializationContext jsonSerializationContext) {
        JsonObject obj = new JsonObject();
        obj.addProperty("author", comment.author);
        obj.addProperty("content", comment.content);
        return obj;
    }
}

On modifie par la suite l’appel à la méthode renderJSon dans le fichier app/controllers/Application.java:

1
2
3
4
5
6
...
    public static void index() {
        List<Post> posts = Post.findAll();
        renderJSON(posts, PostJSonSerializer.get(), CommentJSonSerializer.get());
    }
...

Résultat final

Rafraîchissez l’adresse http://localhost:9000 pour obtenir le résultat suivant :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[
   {
      "author":"moi",
      "title":"Post 1",
      "comments":[
         {
            "author":"moi",
            "content":"du Contenu"
         },
         {
            "author":"moi",
            "content":"du Contenu"
         }
      ]
   },
   {
      "author":"moi",
      "title":"Post 2",
      "comments":[
         {
            "author":"moi",
            "content":"du Contenu"
         }
      ]
   }
]

ps: Je vous livre ici mes observations sur le fonctionnement des JSonSerializer. Si vous voyez une façon de mieux les écrire, ou de mieux faire n’hésitez pas à commenter ce billet.

 

Considérons le contrôleur suivant :

app/controllers/Application.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package controllers;
 
import play.mvc.After;
import play.mvc.Before;
import play.mvc.Controller;
 
public class Application extends Controller {
 
    @Before
    static void before() {
        System.out.println("before");
        renderArgs.put("nom", "Michel");
    }
 
    @After
    static void after() {
        System.out.println("after");
        renderArgs.put("nom", "Serge");
    }
 
    public static void index() {
        System.out.println("index");
        redirect();
    }
    public static void redirect() {
        System.out.println("redirect");
        renderArgs.put("nom", "Robert");
        render();
    }
}

Ainsi que le template associé :
app/views/Application/redirect.html

1
2
3
4
#{extends 'main.html' /}
#{set title:'Home' /}
 
${nom}

Lequel des trois prénoms suivants, Michel, Serge et Robert, va t’il s’afficher dans votre navigateur à l’adresse http://localhost:9000 ?

La réponse est Robert.

Conclusion : Un renderArgs.put dans un @After ne sert à rien!

 

Imaginez que vous ayez un objet Model Tag, et que vous souhaitiez avoir un contrôleur dédié à la gestion de ces objets. Vous le nommeriez tout naturellement controllers.Tags.

Ce qui implique que vous ayez un répertoire app/view/Tags pour vos templates.

Ensuite imaginez que vous ayez besoin pour vos templates de créer des tags Play! personnalisés. Le framework s’attend à les avoir dans un répertoire nommé app/view/tags.

Vous commencez à voir où il pourrait y avoir un problème ?

Et oui, Play! sur Windows (qui est un système que je conchie) hérite du “case insensitive” de l’OS. Ce qui fait que pour lui, il n’y a pas de différence entre app/view/Tags et app/view/tags.
Play! sur Linux, qui lui est case sensitive, voit une différence, et donc ne trouve pas vos templates de tags placés dans le répertoire app/view/Tags.

En conclusion il faut donc faire attention à ne pas nommer un de vos contrôleurs Tags pour éviter tout problème avec les mécanismes internes du framework.

 

Je commence cette série de snippet pour Play! Framework par une méthode très simple pour gérer une authentification “Basic” :

Ajouter à votre contrôleur la méthode @Before suivante :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyController extends Controller {
    @Before
    static void basicAuth() {
        if (StringUtils.isBlank(request.user) && StringUtils.isBlank(request.password)) {
            unauthorized("Veuillez saisir vos identifiants");
        } else {
            User user = User.find("login=? and password=?", request.user, request.password).first();
            if (user == null) {
                unauthorized("Identifiants inconnus");
            }
        }
    }
 
    public static void index() {
        render();
    }
}

Ainsi, toutes les actions que vous appellerez dans ce contrôleur devront être authentifiées pour aboutir.

 

Soirée de fin d’année 2010 exceptionnelle !

Venez nombreux le mardi 14 décembre à partir de 19h00 à l’eXia/CESI de Rouen/Mont Saint Aignan
Avec deux sujets phare du moment, le Normandy JUG termine l’année en beauté !

HTML5 & GIT

Inscriptions Obligatoire sur JugEvents.org

Continue reading »

© 2011 NooCodeCommit Suffusion theme by Sayontan Sinha