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.
Cambridge Templates with Built-in Expression Language
Here is the performance of Cambridge templates if you use the built-in expression language.
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.
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
```
${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}
Entries
```
As you see, Cambridge template code is much shorter compared to Play Framework template code.