Using Controllers in Play Framework

September 20, 2011

Java

«»

Rendering JSON output

As soon as a web application consists of a very fast frontend, no or seldom complete page
reloads occur. This implies a complete rendering by the browser, which is one of the most time
consuming tasks when loading a webpage in the browser. This means you have to get the
data as fast as possible to the client. You can either send them as pre-rendered HTML, XML,
or JSON format. Sending the data in either JSON or XML means the application on the browser
side can render the data itself and decide how and when it should be displayed. This means
your application should be able to create JSON or XML-based responses.

As JSON is quite popular, this example will not only show you how to return the JSON
representation of an entity, but also how to make sure sensitive data such as a password
will not get sent to the user.

Furthermore some hypermedia content will be added to the response, like an URL where more
information can be found.

You can find the source code of this example in the chapter2/json-render-properties
directory.

Getting ready

Beginning with a test is always a good idea:


	public class sonRenderTest extends FunctionalTest {
		@Test
		public void testThatJsonRenderingWorks() {
			Response response = GET("/user/1");
			assertIsOk(response);
			User user = new Gson().fromJson(getContent(response), User.
				class);
			assertNotNull(user);
			assertNull(user.password);
			assertNull(user.secrets);
			assertEquals(user.login, "alex");
			assertEquals(user.address.city, "Munich");
			assertContentMatch("\"uri\":\"/user/1\"", response);
		}
	}

This expects a JSON reply from the request and parses it into a User instance with the help
of gson, a JSON library from Google, which is also used by Play for serializing. As we want to
make sure that no sensitive data is sent, there is a check for nullified values of password and
secrets properties. The next check goes for a user property and for a nested property inside
another object. The last check has to be done by just checking for an occurrence of the string,
because the URL is not a property of the user entity and is dynamically added by the special
JSON serializing routine used in this example.

How to do it…

Create your entities first. This example consists of a user, an address, and a SuperSecretData
entity:


	@Entity
	public class User extends Model {
		@SerializedName("userLogin")
		public String login;
		@NoJsonExport
		public String password;
		@ManyToOne
		public Address address;
		@OneToOne
		public SuperSecretData secrets;
		public String toString() {
			return id + "/" + login;
		}
	}
	@Entity
	public class Address extends Model {
		public String street;
		public String city;
		public String zip;
	}
	@Entity
	public class SuperSecretData extends Model {
		public String secret = "foo";
	}

The controller is simple as well:


	public static void showUser(Long id) {
		User user = User.findById(id);
		notFoundIfNull(user);
		renderJSON(user, new UserSerializer());
	}

The last and most important part is the serializer used in the controller above:


	public class UserSerializer implements JsonSerializer<User> {
		public JsonElement serialize(User user, Type type,
		JsonSerializationContext context) {
			Gsongson = new GsonBuilder()
				.setExclusionStrategies(new LocalExclusionStrategy())
				.create();
			JsonElementelem = gson.toJsonTree(user);
			elem.getAsJsonObject().addProperty("uri", createUri(user.id));
			return elem;
		}
		private String createUri(Long id) {
			Map<String,Object> map = new HashMap<String,Object>();
			map.put("id", id);
			return Router.reverse("Application.showUser", map).url;
		}
		public static class LocalExclusionStrategy implements
		ExclusionStrategy {
			public booleanshouldSkipClass(Class<?>clazz) {
				return clazz == SuperSecretData.class;
			}
			public booleanshouldSkipField(FieldAttributes f) {
				return f.getAnnotation(NoJsonExport.class) != null;
			}
		}
	}

How it works…

The entities used in this example are simple. The only differences are the two annotations in
the User entity. First there is a SerializedNamed annotation , which uses the annotation
argument as field name in the json output – this annotation comes from the gson library.
The @NoJsonExport annotation has been specifically created in this example to mark
fields that should not be exported like a sensitive password field in this example. The address
field is only used as an example to show how many-to-many relations are serialized in the
JSON output.

As you might guess, the SuperSecretData class should mark the data as secret, so this
field should not be exported as well. However, instead of using an annotation, the functions
of the Google gson library will be utilized for this.

The controller call works like usual except that the renderJson() method gets a specific
serializer class as argument to the object it should serialize.

The last class is the UserSerializer class , which is packed with features, although it
is quite short. As the class implements the JsonSerializer class, it has to implement
the serialize() method . Inside of this method a gson builder is created, and a specific
exclusion strategy is added. After that the user object is automatically serialized by the
gson object. Lastly another property is added. This property is the URI of the showUser()
controller call, in this case something like /user/{id} . You can utilize the Play internal
router to create the correct URL.

The last part of the serializer is the ExclusionStrategy, which is also a part of the
gsonserializer. This strategy allows exclusion of certain types of fields. In this case the method
shouldSkipClass() excludes every occurrence of the SuperSecretData class, where the
method shouldSkipFields() excludes fields marked with the @NoJsonExport annotation.

There’s more…

If you do not want to write your own JSON serializer you could also create a template ending
with .json and write the necessary data like in a normal HTML template. However there is
no automatic escaping, so you would have to take care of that yourself.

More about Google gson

Google gson is a pretty powerful JSON library. Once you get used to it and learn to utilize
its features it may help you to keep your code clean. It has very good support for specific
serializers and deserializers, so make sure you check out the documentation before trying
to build something for yourself. Read more about it at http://code.google.com/p/
google-gson/.

Alternatives to Google gson

Many developers do not like the gson library at all. There are several alternatives. There is
a nice example of how to integrate FlexJSON. Check it out at http://www.lunatechresearch.
com/archives/2011/04/20/play-framework-better-jsonserialization-
flexjson.

Writing your own renderRSS method as controller output

Nowadays, an almost standard feature of web applications is to provide RSS feeds,
irrespective of whether it is for a blog or some location-based service. Most clients can handle
RSS out of the box. The Play examples only carry an example with hand crafted RSS feeds
around. This example shows how to use a library for automatic RSS feed generation by getting
the newest 20 post entities and rendering it either as RSS, RSS 2.0 or Atom feed.

You can find the source code of this example in the chapter2/render-rss directory.

Getting ready

As this recipe makes use of the ROME library to generate RSS feeds, you need to download
ROME and its dependency JDOM first. You can use the Play dependency management feature
again. Put this in your conf/dependencies.yml:


	require:
		- play
		- net.java.dev.rome -> rome 1.0.0
	

Now as usual a test comes first:


	public classFeedTest extends FunctionalTest {
		@Test
		public void testThatRss10Works() throws Exception {
			Response response = GET("/feed/posts.rss");
			assertIsOk(response);
			assertContentType("application/rss+xml", response);
			assertCharset("utf-8", response);
			SyndFeed feed = getFeed(response);
			assertEquals("rss_1.0", feed.getFeedType());
		}
		@Test
		public void testThatRss20Works() throws Exception {
			Response response = GET("/feed/posts.rss2");
			assertIsOk(response);
			assertContentType("application/rss+xml", response);
			assertCharset("utf-8", response);
			SyndFeed feed = getFeed(response);
			assertEquals("rss_2.0", feed.getFeedType());
		}
		@Test
		public void testThatAtomWorks() throws Exception {
			Response response = GET("/feed/posts.atom");
			assertIsOk(response);
			assertContentType("application/atom+xml", response);
			assertCharset("utf-8", response);
			SyndFeed feed = getFeed(response);
			assertEquals("atom_0.3", feed.getFeedType());
		}
		private SyndFeedgetFeed(Response response) throws Exception {
			SyndFeedInput input = new SyndFeedInput();
			InputSource s = new InputSource(IOUtils.toInputStream
				(getContent(response)));
			return input.build(s);
		}
	}

This test downloads three different kinds of feeds, rss1, rss2, and atom feeds, and checks
the feed type for each. Usually you should check the content as well, but as most of it is made
up of random chars at startup, it is dismissed here.

How to do it…

The first definition is an entity resembling a post:


	@Entity
	public class Post extends Model {
		public String author;
		public String title;
		public Date createdAt;
		public String content;
		public static List<Post>findLatest() {
			return Post.findLatest(20);
		}
		public static List<Post>findLatest(int limit) {
			return Post.find("order by createdAt DESC").fetch(limit);
		}
	}

A small job to create random posts on application startup, so that some RSS content can be
rendered from application start:


	@OnApplicationStart
	public class LoadDataJob extends Job {
		// Create random posts
		public void doJob() {
			for (int i= 0 ; i< 100 ; i++) {
				Post post = new Post();
				post.author = "Alexander Reelsen";
				post.title = RandomStringUtils.
				randomAlphabetic(RandomUtils.nextInt(50));
				post.content = RandomStringUtils.
				randomAlphabetic(RandomUtils.nextInt(500));
				post.createdAt = new Date(new Date().getTime() +
				RandomUtils.nextInt(Integer.MAX_VALUE));
				post.save();
			}
		}
	}

You should also add some metadata in the conf/application.conf file:


	rss.author=GuybrushThreepwood
	rss.title=My uber blog
	rss.description=A blog about very cool descriptions

The routes file needs some controllers for rendering the feeds:


	GET / Application.index
	GET /feed/posts.rss Application.renderRss
	GET /feed/posts.rss2 Application.renderRss2
	GET /feed/posts.atom Application.renderAtom
	GET /post/{id} Application.showPost

The Application controller source code looks like this:


	import static render.RssResult.*;
	public class Application extends Controller {
		public static void index() {
			List<Post> posts = Post.findLatest(100);
			render(posts);
		}
		public static void renderRss() {
			List<Post> posts = Post.findLatest();
			renderFeedRss(posts);
		}
		public static void renderRss2() {
			List<Post> posts = Post.findLatest();
			renderFeedRss2(posts);
		}
		public static void renderAtom() {
			List<Post> posts = Post.findLatest();
			renderFeedAtom(posts);
		}
		public static void showPost(Long id) {
			List<Post> posts = Post.find("byId", id).fetch();
			notFoundIfNull(posts);
			renderTemplate("Application/index.html", posts);
		}
	}

You should also adapt the app/views/Application/index.html template to show
posts and to put the feed URLs in the header to make sure a browser shows the RSS logo
on page loading:


	#{extends 'main.html' /}
	#{set title:'Home' /}
	#{set 'moreHeaders' }
	<link rel="alternate" type="application/rss+xml" title="RSS 1.0 Feed"
		href="@@{Application.renderRss2()}" />
	<link rel="alternate" type="application/rss+xml" title="RSS 2.0 Feed"
		href="@@{Application.renderRss()}" />
	<link rel="alternate" type="application/atom+xml" title="Atom Feed"
		href="@@{Application.renderAtom()}" />
	#{/set}
	#{list posts, as:'post'}
	<div>
		<h1>#{a @Application.showPost(post.id)}${post.title}#{/a}</h1><br />
		by ${post.author} at ${post.createdAt.format()}
		<div>${post.content.raw()}</div>
	</div>
	#{/list}

You also have to change the default app/views/main.html template, from which all other
templates inherit to include the moreHeaders variable:


	<html>
		<head>
		<title>#{get 'title' /}</title>
		<meta http-equiv="Content-Type" content="text/html;
			charset=utf-8">
		#{get 'moreHeaders' /}
		<link rel="shortcut icon" type="image/png" href="@{'/public/
			images/favicon.png'}">
		</head>
		<body>
			#{doLayout /}
		</body>
	</html>

The last part is the class implementing the different renderFeed methods. This is again
a Result class:


	public class RssResult extends Result {
		private List<Post> posts;
		private String format;
		public RssResult(String format, List<Post> posts) {
			this.posts = posts;
			this.format = format;
		}
		public static void renderFeedRss(List<Post> posts) {
			throw new RssResult("rss", posts);
		}
		public static void renderFeedRss2(List<Post> posts) {
			throw new RssResult("rss2", posts);
		}
		public static void renderFeedAtom(List<Post> posts) {
			throw new RssResult("atom", posts);
		}
		public void apply(Request request, Response response) {
			try {
				SyndFeed feed = new SyndFeedImpl();
				feed.setAuthor(Play.configuration.getProperty
				("rss.author"));
				feed.setTitle(Play.configuration.getProperty
				("rss.title"));
				feed.setDescription(Play.configuration.getProperty
				("rss.description"));
				feed.setLink(getFeedLink());
				List<SyndEntry> entries = new ArrayList<SyndEntry>();
				for (Post post : posts) {
					String url = createUrl("Application.showPost", "id",
					post.id.toString());
					SyndEntry entry = createEntry(post.title, url,
					post.content, post.createdAt);
					entries.add(entry);
				}
				feed.setEntries(entries);
				feed.setFeedType(getFeedType());
				setContentType(response);
				SyndFeedOutput output = new SyndFeedOutput();
				String rss = output.outputString(feed);
				response.out.write(rss.getBytes("utf-8"));
			} catch (Exception e) {
				throw new UnexpectedException(e);
			}
		}
		private SyndEntrycreateEntry (String title, String link, String
		description, Date createDate) {
			SyndEntry entry = new SyndEntryImpl();
			entry.setTitle(title);
			entry.setLink(link);
			entry.setPublishedDate(createDate);
			SyndContententryDescription = new SyndContentImpl();
			entryDescription.setType("text/html");
			entryDescription.setValue(description);
			entry.setDescription(entryDescription);
			return entry;
		}
		private void setContentType(Response response) {
			...
		}
		private String getFeedType() {
			...
		}
		private String getFeedLink(){
			...
		}
		private String createUrl(String controller, String key, String
		value) {
			...
		}
	}

How it works...

This example is somewhat long at the end. The post entity is a standard model entity with a
helper method to find the latest posts. The LoadDataJob fills the in-memory database on
startup with hundreds of random posts.

The conf/routes file features showing an index page where all posts are shown, as well as
showing a specific post and of course showing all three different types of feeds.

The controller makes use of the declared findLatest() method in the post entity to get the
most up-to-date entries. Furthermore the showPost() method also utilizes the index.html
template so you do not need to create another template to view a single entry. All of the used
renderFeed methods are defined in the FeedResult class.

The index.html template file features all three feeds in the header of the template. If you
take a look at app/views/main.html, you might notice the inclusion of the moreHeaders
variable in the header. Using the @@ reference to a controller in the template creates absolute
URLs, which can be utilized by any browser.

The FeedResult class begins with a constructor and the three static methods used in the
controller, which render RSS, RSS 2.0, or Atom feeds appropriately.

The main work is done in the apply() method of the class. A SyndFeed object is
created and filled with meta information like blog name and author defined in the
application.conf file.

After that a loop through all posts generates entries, each including the author, content, title,
and the generated URL. After setting the content type and the feed type—RSS or atom—the
data is written to the response output stream.

The helper methods have been left out to save some lines inside this example. The
setContentType() method returns a specific content type, which is different for RSS
and atom feeds. The getFeedType() method returns "rss_2.0", "rss_1.0", or "atom_0.3"
depending on the feed to be returned. The getFeedLink() method returns an absolute URL
for any of the three feed generating controller actions. The createUrl() method is a small
helper to create an absolute URL with a parameter which in this case is an ID. This is needed
to create absolute URLs for each post referenced in the feed.

The example also uses ROME to extract the feed data again in the test, which is not
something you should do to ensure the correct creation of your feed. Either use another
library, or, if you are proficient in checking corner cases by hand, do it manually.

There's more...

This is (as with most of the examples here) only the tip of the iceberg. Again, you could also
create a template to achieve this, if you wanted to keep it simple. The official documentation
lists some of the preparing steps to create your own templates ending with .rss at

http://www.playframework.org/documentation/1.2/routes#Customformats

Using annotations to make your code more generic

This implementation is implementation specific. You could make it far more generic with the
use of annotations at the Post entity:


	@Entity
	public class Post extends Model {
		@FeedAuthor
		public String author;
		@FeedTitle
		public String title;
		@FeedDate
		public Date createdAt;
		@FeedContent
		public String content;
	}

Then you could change the render signature to the following:


	public RssResult(String format, List<? extends Object> data) {
		this.data = data;
		this.format = format;
	}
	public static void renderFeedRss(List<? extends Object> data) {
		throw new RssResult("rss", data);
	}

By using the generics API you could check for the annotations defined in the Post entity and
get the content of each field.

Using ROME modules

ROME comes with a bunch of additional modules. It is pretty easy to add GeoRSS information
or MediaWiki-specific RSS. This makes it pretty simple to extend features of your feeds.

Cache at the right place

Especially RSS URLs are frequently under heavy loads – one misconfigured client type can
lead to some sort of denial of service attack on your system. So again a good advice is to
cache here. You could cache the SyndFeed object or the rss string created out of it. However,
since play 1.1 you could also cache the complete controller output. See Basics of caching at
the beginning of Chapter 4 for more information about that.

email

«»

Comments

comments