Jump into Java microframeworks, Part 4: Play

A closer look at Java's original, ultra-lightweight web development stack

Although Play is known as a microframework, really it's a total web development stack, including a build tool that closely integrates with application provisions like view and persistence support. You installed Play in Part 1, and got a quick tour of its programming basics. Now we'll extend that basic application to explore some exciting capabilities of Play.

As with the other deep dives, we'll begin by adding persistence to the basic app from Part 1. If you don't already have it, use the link below to download the source code for the Play demo app.

download
Source code for the Play demo app. Created by Matthew Tyson for JavaWorld.

Connecting Play to a database

Listing 1 has the schema for the Play demo app. Note that we'll be using a MariaDB instance running on localhost.

Listing 1. DDL for the Play example app


create table groups (name varchar (200), id int not null auto_increment primary key);

Our first step is to tell Play how to connect to your database using MySQL. Open up application.conf, and insert the connection properties as I have done in Listing 2. You'll notice I left some comments in there. You'll also see the comments in your configuration file, because Play includes them as an example of connecting to the H2 database. I've used root and password for this example, but you shouldn't do that in a real app. You can use a different RDBMS if you prefer; just check the documentation for the specific connection parameters you'll need.

Listing 2. Database settings for application.conf


#db.default.driver=org.h2.Driver
#db.default.url="jdbc:h2:mem:play"
#db.default.username=sa
#db.default.password=""
db.default.driver=com.mysql.jdbc.Driver
db.default.url="mysql://root:password@localhost/play_app"

So far so good -- Play knows how to communicate with the MariaDB instance. Now let's consider our options for getting the data in and out from the app. Ninja had JPA/Hibernate bundled in, so we used that. For Spark we mixed a custom cocktail of DBUtils and Boon JSON. For Play we'll use EBean, a lightweight ORM that is similar in spirit to Hibernate but with lighter configuration. (Note that Play is capable of running a full JPA provider if you need that.)

It's your choice whether to start with configuration or code. Personally, I decide using some combination of celestial alignment and barometric pressure. For this project we'll start with the code, by creating a model class. In Part 1 we created a Group class in order to model a musical group. Now we'll extend the Group class into an Entity bean. Listing 3 shows the modified class.

Listing 3. An EBean-enabled model class


package models;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import com.avaje.ebean.Model;

@Entity
@Table(name="groups")
public class Group extends Model {
	@Id
	@GeneratedValue
	String id;
	String name;
	
	public Group(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

The Group class in Listing 3 is in the models package, which is in the /app/models path. Note that this path lives at the same level as the /controllers and /views directories. Figure 1 shows the file's placement in Eclipse.

Java microframeworks Part 4: Figure 1

Figure 1. The Group class in models

Using standard JPA annotations, @Entity denotes that the Group class is persistent, while @Table(name="groups") alters the name of the table when mapping the class to SQL. We need to do this because group is a reserved name in SQL -- MariaDB won't like it if we try to select * from group.

We also annotate the id field using@Id and @GeneratedValue, because we made that an auto-incremented field in our DB table. In keeping with the JPA convention, the field name will be persisted without any additional annotations.

Configure the ORM

Next, we want to enable the EBean plugin that came with Play, and we do that in the project/plugins.sbt file. Listing 4 shows the line that you want to ensure is uncommented in plugins.sbt.

Listing 4. Adding the EBean plugin dependency


addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "1.0.0")

We've enabled EBean as a plugin but we still have to tell Play to use it in our project. We do that by adding it to the build definition file, build.sbt. You can see how this is done in Listing 5. Notice the project name is microPlay (comment 1) and that the name is referenced in the line commented as 2.

Listing 5. Modifications to build.sbt, including enabling PlayEbean


addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "1.0.0")
name := "microPlay" // 1

version := "1.0-SNAPSHOT"

lazy val microPlay = (project in file(".")).enablePlugins(PlayJava, PlayEbean) // 2

scalaVersion := "2.11.6"

libraryDependencies ++= Seq(
  javaJdbc,
  cache,
  javaWs,
  "mysql" % "mysql-connector-java" % "5.1.18" 
) // 3

// Play provides two styles of routers, one expects its actions to be injected, the
// other, legacy style, accesses its actions statically.
routesGenerator := InjectedRoutesGenerator


//fork in run := true //4

In line 2 we just add PlayEbean to our enablePlugins call -- simple enough. Also in build.sbt (comment 3), we add the MySQL driver as a dependency (mysql" % "mysql-connector-java" % "5.1.18). This is done by adding the dependency to the libraryDependency Sequence, which is just a Scala collection. The format uses the same semantics but different syntax than a Maven dependency; notice how the group ID, artifact ID, and version are separated by percentage signs.

Finally, in Listing 5, notice the line commented as 4. This line isn't normally commented when Play builds your app, but I observed very poor compile and load times when it was enabled, along with numerous timeouts, so your mileage may vary. The issue is addressed in this StackOverflow question.

One more thing ...

We're done with build.sbt, but we're not quite done with configuration. We need to tell EBean where our persistent classes are. To do this, you'll open application.conf and add the line from Listing 6 to the end of the file. Here we're telling the Play EBean plugin that our mapped entities are in the /models directory.

Listing 6. Setting the EBean model location


ebean.default = ["models.*"]

Configure the app URL and interface

We now have a persistent model class and the infrastructure to use it, but we have no way to get at it. We can address that by mapping a URL in the routes file, as seen in Listing 7.

Listing 7. Adding a group create URL


POST    /group						controllers.Application.createGroup()

Next, we'll create a form on the app's index page, index.html.scala, which you might recall from Part 1. Add the code in Listing 8 to the index page.

Listing 8. HTML form to create a group


<form action="@routes.Application.createGroup()" method="post">
	<input type="text" name="name"></input>
	<button>Create Group</button>
</form>

By setting @routes.Application.createGroup() as the action for the above form, we tell Play to route submitted requests to that endpoint. This is a reverse route -- essentially, we're telling Play to find whatever URL will get us the Application.createGroup() method, and supply that. If you inspect the source of the page you'll see that the action is set to /groups. You can also see this in Figure 2.

Java microframeworks Part 4: Figure 2

Figure 2. Routing a request

Before we can handle the request, we need a createGroup() method on our controller. So, open up app/controllers/Application.java and add the method in Listing 9.

Listing 9. Application.java: createGroup() handler


 public Result createGroup() {
    	Group group = Form.form(Group.class).bindFromRequest().get();
    	group.save();
    	return redirect(routes.Application.index());
    }

Listing 9 shows the tools we can use in Play to extract the request into an object. The method is a three-liner: from getting the entity, to saving it, to returning a redirect. The Form.form() utility function takes a class (in this case, the Group class), allowing us to bind the request into a new instance. We can then simply call group.save() on the instance, because we've done the work of mapping it via EBean. Note that we're dealing less with the ORM solution in this case than we would with a full JPA lineup; we don't ever directly handle or need to inject an EntityManager. Finally, the redirect allows us to again use a reverse route to forward on to the routes.Application.index() handler.

Make it RESTful

So far we've been developing a pretty traditional web application. The difference between that and a single-page AJAX-style REST-submitted app is just a few lines of JavaScript.

Look again at the routes file and notice this line:


GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)

This maps GET requests to the /assets URL to the built-in controller called Assets. It uses the versioned method, routing them via the first argument to /public. Versioned means that these assets will be tagged with a cache-busting string when a new version is deployed in production. As a result, clients will always get the most recent version. (Note that the second argument, file:Asset, is required for versioning to work. Also note that versioning doesn't work in dev mode!)

For this solution to work, our JavaScript files need to be in the /public folder. Fortunately, Play already has a /javascripts folder, which includes a friendly hello.js to get us started. We'll use this script shortly. In Figure 3 you can see for yourself where this folder and file are in the Eclipse navigator.

Java microframeworks Part 4: Figure 3

Figure 3. JavaScripts folder in /public

Adding JavaScript elements

The first thing we'll do is add jQuery to the application dependencies. We could paste in jQuery to /public, or reference a public CDN. An easier option is to pull the dependency into our project using WebJars. Like Ninja, Play has built-in support for WebJars. To add jQuery, we'll modify our build.sbt to include it, as seen in Listing 10. Just for fun, I've used the bleeding-edge (as of this writing) jQuery version in Alpha (3.0.0-alpha1).

Listing 10. Adding jQuery WebJar to build.sbt

libraryDependencies ++= Seq( javaJdbc, cache, javaWs, "mysql" % "mysql-connector-java" % "5.1.18", "org.webjars" %% "webjars-play" % "2.4.0" "org.webjars" % "jquery" % "3.0.0-alpha1" )

Notice that I actually added two new dependencies: the Play WebJar library, and a reference to the jQuery webjar.

Now we just need to reference the jQuery webjar from our HTML header. Listing 11 shows how to do that. We'll add the line from Listing 8 to main.html.scala. Just make sure to put the line before the hello.js include (because hello.js is going to reference jQuery here in just a moment).

Listing 11. index.html.scala: Adding jQuery webjar reference

 

In Listing 11 we use Play's support for WebJar (added in Listing 7) to refer to the jQuery library. Lastly, we need to map a route for it in routes. We only have to do this the first time we set up WebJar. Listing 12 shows the route we want.

Listing 12. Adding WebJar Route


GET     /webjars/*file                    controllers.WebJarAssets.at(file)

To find the WebJar you need, go to http://www.webjars.org/ and type in what you're looking for. Then, select your build tool and cut-and-paste the dependency string into your build file. Figure 4 has a screenshot of the process.

Java microframeworks Part 4: Figure 4

Figure 4. Loading a WebJar

Now jQuery is available to our front-end. We also want to include a small jQuery plugin, serializeObject, which will help us with the form submit. Copy the source of that GitHub project into a /public/javascripts/serializeObject.js file, placing it right next to hello.js in your javascripts folder. Also include it in the main.html.scala file (along with hello.js), as shown in Listing 13.

Listing 13. Referencing serializeObject.js


<script src="@routes.Assets.versioned("javascripts/serializeObject.js")" type="text/javascript"></script>

Now we'll add our own JavaScrip, using hello.js as a base. Start by adding the JavaScript in Listing 14 to hello.js.

Listing 14. hello.js: Adding JavaScript support for addGroup


App = {
		startup: function(){
			$("#addGroupButton").click(App.addGroup);
		},
		addGroup: function(){
			var group = $('#groupForm').serializeObject();
			$.ajax({
		        url: '/group',
		        type: 'POST',
		        contentType: 'application/json',
		        data: JSON.stringify(group),
		        success: function(){
		        	//App.loadPeople();
		        },
		        error: function(xhr,err){
		        	alert("encountered a problem: ", err);
		        }
		    });
			return false;
		}
}
$( document ).ready(function() {
   App.startup();
});

Related:
1 2 Page 1
Shop Tech Products at Amazon