Export to GitHub

cambridge - UsingWithPlayFramework.wiki


Introduction

Although Play Framework has its own Templating mechanism which is based on Groovy, it is known that the template engine it provides isn't the part where the framework shines.

Using the cambridge-playframework module, it is very easy to integrate Cambridge templates into Play Framework.

Installing

In addition to regular Cambridge jars (cambridge-core.jar, antlr-runtime.jar, stringtemplate.jar) you also need cambridge-playframework.jar in the classpath of your play application. (Put all these jar files under lib directory of your application)

Using

Play framework controllers extend play.mvc.Controller by default. The Cambridge - Play framework integration defines play.mvc.CambridgeController which extends and overrides rendering functions of the default base controller.

Instead of extending directly from play.mvc.Controller, if you extend play.mvc.CambridgeController, the Cambridge template engine will be used to render your templates. The path you should put your templates, I18N messages should stay the same.

Using any controller that extends play.mvc.Controller will still use Play Frameworks own template engine.

Extensions to default Cambridge syntax

The @{...} and @@{...} tags for linking to Play Framework actions is supported and should work the same way as in the play framework templates.

The &{...} syntax to load internalization messages is also supported.

Please note: @{...} and @@{...} tags are currently not supported for static resources

Performance Comparison

I did some tests to compare page rendering speeds of Cambridge and Play Framework Templates. Using a heavy template model, the same exact page was rendered about 1000 times each using Apache JMeter. Both the templates were using template inheritance. My tests show that Cambridge templates perform about 3.5 times faster than Play Framework's own template engine.

Here are the test results that I got from JMeter:

Play Templates

Here is the result of 10 threads making 1000 requests in total to render the same Play Template. http://cambridge.googlecode.com/svn/wiki/Play.png

Cambridge Templates with Built-in Expression Language

Here is the performance of Cambridge templates if you use the built-in expression language. http://cambridge.googlecode.com/svn/wiki/Cambridge.png

Cambridge Templates with MVEL Expression Language

If you use MVEL as your expression language, the performance is slightly slower compared to built-in expression language but very similar results. http://cambridge.googlecode.com/svn/wiki/Mvel.png

Test Application

You can download the sample application in the downloads section.

Here is the code for both Play Framework and Cambridge that was used in test.

Data Model Code

The following exact randomly generated data model is used in both templates. The data generation gets complete before the tests start.

DataModel.java ``` package models;

import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Random;

/** * @author Erdinc YILMAZEL * @since 1/30/11 */ public class DataModel { public static DataModel dataModel = new DataModel(100, 1000, 1500);

public static class User { int userId; String userName; String email; boolean active; Date registryDate;

  ArrayList<User> friends;

  public int getUserId() {
     return userId;
  }

  public String getUserName() {
     return userName;
  }

  public String getEmail() {
     return email;
  }

  public boolean isActive() {
     return active;
  }

  public ArrayList<User> getFriends() {
     return friends;
  }

  public Date getRegistryDate() {
     return registryDate;
  }

  public void setUserId(int userId) {
     this.userId = userId;
  }

  public void setUserName(String userName) {
     this.userName = userName;
  }

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

  public void setActive(boolean active) {
     this.active = active;
  }

  public void setRegistryDate(Date registryDate) {
     this.registryDate = registryDate;
  }

  public void setFriends(ArrayList<User> friends) {
     this.friends = friends;
  }

}

public static class Entry implements Comparable { int entryId; String title; String body; User owner; Date entryDate; ArrayList comments;

  public int compareTo(Entry o) {
     return entryDate.compareTo(o.entryDate);
  }

  public int getEntryId() {
     return entryId;
  }

  public String getTitle() {
     return title;
  }

  public String getBody() {
     return body;
  }

  public User getOwner() {
     return owner;
  }

  public Date getEntryDate() {
     return entryDate;
  }

  public ArrayList<Comment> getComments() {
     return comments;
  }

  public void setEntryId(int entryId) {
     this.entryId = entryId;
  }

  public void setTitle(String title) {
     this.title = title;
  }

  public void setBody(String body) {
     this.body = body;
  }

  public void setOwner(User owner) {
     this.owner = owner;
  }

  public void setEntryDate(Date entryDate) {
     this.entryDate = entryDate;
  }

  public void setComments(ArrayList<Comment> comments) {
     this.comments = comments;
  }

}

public static class Comment { int commentId; Entry entry; User owner; String commentText;

  public int getCommentId() {
     return commentId;
  }

  public Entry getEntry() {
     return entry;
  }

  public User getOwner() {
     return owner;
  }

  public String getCommentText() {
     return commentText;
  }

  public void setCommentId(int commentId) {
     this.commentId = commentId;
  }

  public void setEntry(Entry entry) {
     this.entry = entry;
  }

  public void setOwner(User owner) {
     this.owner = owner;
  }

  public void setCommentText(String commentText) {
     this.commentText = commentText;
  }

}

public HashMap userList; public HashMap entryList; public ArrayList entries; public User loggedInUser; final int userCount; final int entryCount; final int commentCount; final Random random = new Random();

public DataModel(int userCount, int entryCount, int commentCount) { this.userCount = userCount; this.entryCount = entryCount; this.commentCount = commentCount;

  userList = new HashMap<Integer, User>();
  entryList = new HashMap<Integer, Entry>();
  entries = new ArrayList<Entry>();

  // Create Users
  for (int i = 0; i < userCount; i++) {
     User user = new User();
     user.userId = i + 10001;
     user.userName = "User" + user.userId;
     user.active = random.nextInt(10) < 3;
     user.email = user.userName + "@domain.com";
     user.friends = new ArrayList<User>();

     userList.put(user.userId, user);
  }

  // Create Friends List
  for (int i = 0; i < userCount; i++) {
     int userId = i + 10001;
     User user = userList.get(userId);
     HashSet<Integer> friends = new HashSet<Integer>();
     friends.add(user.userId);
     int size = random.nextInt(5) + 1;
     while (friends.size() < size) {
        int friendId = random.nextInt(userCount) + 10001;
        if (!friends.contains(friendId)) {
           friends.add(friendId);
           user.friends.add(userList.get(friendId));
        }
     }
  }

  long oneMonth = 30 * 24 * 60 * 60 * 1000L;
  long aMonthAgo = System.currentTimeMillis() - oneMonth;

  for (int i = 0; i < entryCount; i++) {
     Entry entry = new Entry();
     entry.entryId = i + 10001;
     entry.title = "Entry title " + entry.entryId;
     entry.body = "Entry body " + entry.entryId;
     entry.owner = getRandomUser();
     entry.entryDate = new Date(aMonthAgo + random.nextInt((int) (oneMonth / 1000L)));
     entry.comments = new ArrayList<Comment>();
     entryList.put(entry.entryId, entry);
     entries.add(entry);
  }

  Collections.sort(entries);

  for (int i = 0; i < commentCount; i++) {
     Comment comment = new Comment();
     comment.commentId = i + 10001;
     comment.owner = getRandomUser();
     comment.entry = entryList.get(random.nextInt(entryCount) + 10001);
     comment.commentText = "Comment text " + comment.commentId;

     comment.entry.comments.add(comment);
  }

  loggedInUser = getRandomUser();

}

public User getRandomUser() { return userList.get(random.nextInt(userCount) + 10001); }

public HashMap getUserList() { return userList; }

public HashMap getEntryList() { return entryList; }

public ArrayList getEntries() { return entries; }

public User getUser(int userId) { return userList.get(userId); }

public Entry getEntry(int entryId) { return entries.get(entryId); }

public User getLoggedInUser() { return loggedInUser; } }

```

Play Framework Templates Code

PlayTest.java (Controller) ``` public class PlayTest extends Controller { public static void index() { DataModel model = DataModel.dataModel; DataModel.User loggedInUser = model.getLoggedInUser();

  renderArgs.put("title", "Entries");
  renderArgs.put("loggedInUser", loggedInUser);
  renderArgs.put("entries", model.getEntries());

  render();

} } ```

index.html ```

{extends 'main.html' /}

{set title:'Home' /}

{if entries.size() > 0}

{list items:entries, as:'entry'}

Entry Id: ${entry.entryId}, Date: ${entry.entryDate}

${entry.title}

Submitted By: ${entry.owner.userName} - ${entry.owner.email} ${entry.body} #{if entry.comments.size() > 0} #{list items:entry.comments, as:'comment'} Comment by: ${comment.owner.userName} ${comment.commentText} #{/list} #{/if} #{else} No comments yet #{/else}

{/list}

{/if}

{else}

No Entries Found

{/else}

```

main.html ``` #{get 'title' /}

{if loggedInUser != null}

Hello ${loggedInUser.userName}, You have ${loggedInUser.friends.size()} friends

{/if}

Entries

{doLayout /}

```

Cambridge Code

Cambridge.java (Controller) ``` public class Cambridge extends CambridgeController { public static void index() { DataModel model = DataModel.dataModel; DataModel.User loggedInUser = model.getLoggedInUser();

  renderArgs.put("title", "Entries");
  renderArgs.put("loggedInUser", loggedInUser);
  renderArgs.put("entries", model.getEntries());

  render();

} } ```

index.html ```

0"> Entry Id: ${self.entryId}, Date: ${self.entryDate}(dateformat:EEE, d MMM yyyy)

${self.title}

Submitted By: ${self.owner.userName} - ${self.owner.email} ${self.body} 0" a:foreach="self.comments"> Comment by: ${self.owner.userName} ${self.commentText} No comments yet No Entries Found

```

skeleton.html ``` ${title}

Hello ${loggedInUser.userName}, You have ${loggedInUser.friends.size()} friends

Entries

```

As you see, Cambridge template code is much shorter compared to Play Framework template code.