Skip to content

Latest commit

 

History

History
677 lines (432 loc) · 21.4 KB

JAVA.md

File metadata and controls

677 lines (432 loc) · 21.4 KB

Welcome to the Play2torial for Java!

====================================

This tutorial will help teach you how to build Java web applications with Play Framework 2.3.

Before you get started, you will need to install java, git, sbt, and finally, and older version of the Play Framework.

Many of these install may require you to update your PATH, which is done differently in Windows or the Mac. Those details are not provided in this tutorial.

Test that the activator command works by running:

activator -help

You should see text displaying optional commands for the activator tool. If you do not, ensure you have that the downloaded activator script referenced in your PATH.

Alright! You are ready to go!

Create a Play App

Create a new Play 2.3 application named "play2torial" by running:

activator new play2torial play-java-2.3

This will create a new project called play2torial in your current working directory using the play-java-2.3 template (look here for more templates). Since the release of newer versions of the play framework, all versions of activator will try to use newer templates by default, so you'll have to specify the template manually to use an older version of Play.

In the newly created play2torial directory create a new git repository by running:

git init

Add the files to the git repo and commit them:

git add .
git commit -m init

Throughout this tutorial you will be able to check your progress against the official tutorial. To do this add the official tutorial as a new git remote named upstream by running:

git remote add upstream https://github.com/YogoGit/play2torial.git

Fetch the remote repository:

git fetch upstream

From this point forward, you will no longer be using activator, and instead you will be using sbt.

Set up an IDE

Before we take a tour of the app you can optionally generate project files for Eclipse. This is highly recommended.

For Eclipse run:

sbt eclipse

For IntelliJ, install the Scala IntelliJ Plugin and then open the project's build.sbt file from the File > Open... dialog.

Read more about IDE's

Start the Play Server

Now start the Play app by running:

sbt run

Open the following URL in your browser to verify the app is working:
http://localhost:9000/

Read more about the Play Console

Old Play Framework issues

Because this tutorial is based on an older version of Play, there are potential issues that must be worked-around. In the file 'build.sbt' in your project, please append the following line to the bottom of the file.

PlayKeys.playWatchService := play.sbtplugin.run.PlayWatchService.sbt(pollInterval.value)

Otherwise, you will see errors such as the following when you run the application.

java.lang.UnsatisfiedLinkError: no jnotify in java.library.path
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
...

Commit your changes to build.sbt:

git add build.sbt
git commit -m "Fixed JNotify issues"
git diff java-jnotify_fix

Routes

Play routes HTTP requests to a controller using the routes defined in the conf/routes file. The routes file maps HTTP verbs and URL patterns to controller methods. The route that matched the request you just made was defined in the "routes" file with:

GET     /                           controllers.Application.index()

That means that when a HTTP GET request for the URL / comes in, it will be routed to the method named index on the controllers.Application class.

Add a new route to handle GET requests to /foo with a call to controllers.Application.index() by adding the following line to the conf/routes file:

GET     /foo                        controllers.Application.index()

Now try to open the following URL in your browser:
http://localhost:9000/foo

You should see the same welcome message as your did when you made the request to the / URL.

Read more about Routing

Commit and verify your changes:

git add conf/routes
git commit -m "added new route"
git diff java-foo_route

Test a Route

Now that you have a new route for /foo lets create a test for that route. Now create a new file test/FooTest.java file containing:

import org.junit.Test;
import play.mvc.Result;
import play.test.Helpers;
import play.test.WithApplication;

import static play.test.Helpers.*;
import static org.fest.assertions.Assertions.*;

public class FooTest extends WithApplication {

    @Test
    public void testFooRoute() {
        Result result = route(fakeRequest(GET, "/foo"));
        assertThat(result).isNotNull();
    }

}

That simple test will simulate a call to /foo and validate that the result was not null. Run the test (you can keep the Play server running in another window):

sbt test

You should see something similiar to:

[info] Passed: Total 4, Failed 0, Errors 0, Passed 4

Read more about Testing Routes

Commit and verify your changes:

git add test
git commit -m "added test for foo route"
git diff java-test_route

Update a Controller

The app directory is the main source for a Play app. In that directory there is a directory named controllers containing a file named Application.java which is the controllers.Application class.

The index() method body looks like:

return ok(index.render("Your new application is ready."));

Edit the Application.java file and change the Your new application is ready. string to hello, world. Save the file and then reload the following URL in your browser:
http://localhost:9000/

Notice that the header at the top of the page now reads hello, world. Play recompiled the Java controller behind the scenes. If you had made a change that could not be compiled you would see the compile error in your browser and in your console.

This change will break two of the existing tests so change the Your new application is ready. string to hello, world in the test/ApplicationTest.java and test/IntegrationTest.java files.

To verify the tests pass, run:

sbt test

Commit and verify that your changes:

git commit -am "updated controller"
git diff java-hello_controller

Test a Controller

You can do tests against a controller by simply creating a new JUnit Test. Add a new test method to the test/ApplicationTest.java file:

    @Test
    public void testIndex() {
        Result result = controllers.Application.index();
        assertThat(status(result)).isEqualTo(OK);
        assertThat(contentType(result)).isEqualTo("text/html");
        assertThat(charset(result)).isEqualTo("utf-8");
        assertThat(contentAsString(result)).contains("hello, world");
    }

This simulates a request to the Application.index() controller method and verifies that the response is what we expect. Run the test:

sbt test

You should see something like:

[info] Passed: Total 5, Failed 0, Errors 0, Passed 5

Read more about Testing Controllers

Commit and verify that your changes:

git add test/ApplicationTest.java
git commit -m "added Application controller test"
git diff java-test_controller

Update a View

Play uses Scala for server-side templating. The Application controller renders the views.html.index template which is compiled from the app/views/index.scala.html file. The index template takes a String parameter:

@(message: String)

Then the index template uses the main template (from app/views/main.scala.html) to get a base HTML page:

@main("Welcome to Play") {

The main template is passed a String parameter for the page title and a Html parameter for the body of the page. The body of the page is the Play welcome message which comes from:

@play20.welcome(message, style = "Java")

Change the Play welcome message to uppercase:

@play20.welcome(message.toUpperCase, style = "Java")

View your changes in the browser:
http://localhost:9000/

Because the test/ApplicationTest.java and test/IntegrationTest.java tests use this updated template, update the tests to account for the upper cased string by changing all hello, world strings to HELLO, WORLD.

As you can see, the test/ApplicationTest.java file tests the server-side template directly:

    @Test
    public void renderTemplate() {
        Content html = views.html.index.render("HELLO, WORLD");
        assertThat(contentType(html)).isEqualTo("text/html");
        assertThat(contentAsString(html)).contains("HELLO, WORLD");
    }

The test/IntegrationTest.java does an integration test by starting a Play server and using HtmlUnit to make a headless browser request to the server, thus testing the routes, controller, and views:

    @Test
    public void test() {
        running(testServer(3333, fakeApplication(inMemoryDatabase())), HTMLUNIT, new Callback<TestBrowser>() {
            public void invoke(TestBrowser browser) {
                browser.goTo("http://localhost:3333");
                assertThat(browser.pageSource()).contains("HELLO, WORLD");
            }
        });
    }

Run the tests and verify they all pass:

sbt test

Read more about Templating and Template Use Cases

Commit and verify that your changes:

git commit -am "change the view"
git diff java-hello_view

Create a Model

By default, Play 2.3 with Java uses Ebean for RDBMS persistence. To setup a data source, edit the conf/application.conf file and uncomment these lines:

db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
    
ebean.default="models.*"

This will use an in-memory database for a data source named "default".

Create a new Java class to hold Task objects in a package named models by creating a new app/models/Task.java file containing:

package models;
    
import play.db.ebean.Model;
    
import javax.persistence.Entity;
import javax.persistence.Id;
    
@Entity
public class Task extends Model {
    
    @Id
    public String id;
    
    public String contents;
    
}

Create a test for your model by creating a new test/TaskTest.java file containing:

import org.junit.Test;
    
import static play.test.Helpers.fakeApplication;
import static play.test.Helpers.running;
    
import static org.fest.assertions.Assertions.assertThat;
    
import models.Task;
    
public class TaskTest {
    
    @Test
    public void create() {
        running(fakeApplication(), new Runnable() {
            public void run() {
                Task task = new Task();
                task.contents = "Write a test";
                task.save();
                assertThat(task.id).isNotNull();
            }
        });
    }
    
}

Run the tests and make sure all four pass:

sbt test

Ebean will automatically create a new database evolution script for you in a file named conf/evolutions/default/1.sql.

Read more about EBean ORM Models

Commit and verify your changes:

git add conf/evolutions conf/application.conf app/models/Task.java test/TaskTest.java
git commit -m "added Task model, in-memory database config, and test"
git diff java-task_model

Create UI for Adding Tasks

Start by adding a new method to the app/controllers/Application.java file that will map request parameters to a Task object, save it, and then redirect to the index() page.

Now add a new method named addTask:

    public static Result addTask() {
        play.data.Form<models.Task> form = play.data.Form.form(models.Task.class).bindFromRequest();
        models.Task task = form.get();
        task.save();
        return redirect(routes.Application.index());
    }

Now add a new route to the conf/routes file that will handle POST requests to /task and handle the request with the Application.addTask() method:

POST    /task                       controllers.Application.addTask()

Now create a form in the app/views/index.scala.html template for adding new Tasks. Replace the @play20.welcome line with:

    @message.toUpperCase
          
    @helper.form(action = routes.Application.addTask()) {
        <input name="contents"/>
        <input type="submit"/>
    }

Test out the new UI by loading the app in your browser:
http://localhost:9000/

You will be prompted to run the database evolutions. Just click the Apply this script now! button.

Add a new Task and the browser should refresh the index page.

Read more about Forms and Templates

Commit and verify your changes:

git commit -am "add new UI to create Tasks"
git diff java-task_add

Get Tasks as JSON

Create a new getTasks controller method in the app/controllers/Application.java file:

    public static Result getTasks() {
        java.util.List<models.Task> tasks = new play.db.ebean.Model.Finder(String.class, models.Task.class).all();
        return ok(play.libs.Json.toJson(tasks));
    }

Add a new route to the conf/routes file to get the tasks as JSON serialized data:

GET     /tasks                      controllers.Application.getTasks()

After adding a new Task load the following URL in your browser:
http://localhost:9000/tasks

Verify that you see a Task (or Tasks) in JSON form. If the contents property in the JSON is null, then stop the sbt run process, run sbt clean and then run the app again: sbt run

Commit and verify your changes:

git commit -am "get the tasks as JSON"
git diff java-task_json

Display the Tasks via CoffeeScript and jQuery

Add the jQuery WebJar to your project by adding the following to your build.sbt file after the first libraryDependencies declaration.

libraryDependencies += "org.webjars" % "jquery" % "1.11.2"

Note: Make sure you have an empty line before the line above.

Restart the sbt run process to reload the dependencies.

In the body of the app/views/index.scala.html file add a place to display the tasks above the form:

<ul id="tasks"></ul>

Create a new file named app/assets/javascripts/index.coffee (and create the necessary directories) containing a simple CoffeeScript application that uses jQuery to load and display the tasks:

$ ->
  $.get "/tasks", (data) ->
    $.each data, (index, task) ->
      $("#tasks").append $("<li>").text task.contents

This makes a get request to /tasks for the JSON serialized list of Task objects and then adds a new list item to the page element with the id of tasks for each Task.

Update the app/views/main.scala.html file to load jQuery from the WebJar and include the compiled version of the index.coffee JavaScript:

        <script src="@routes.Assets.at("lib/jquery/jquery.min.js")" type="text/javascript"></script>
        <script src="@routes.Assets.at("javascripts/index.js")" type="text/javascript"></script>

Check out the app in your browser and verify that the list of tasks is displayed:
http://localhost:9000/

To see the compiled version of the compiled javascript you can go to http://localhost:9000/assets/javascripts/index.js

Read more about Assets and CoffeeScript

Commit and verify your changes:

git add app/views/index.scala.html app/assets/javascripts/index.coffee app/views/main.scala.html
git commit -m "Display the list of tasks using jQuery and CoffeeScript"
git diff java-task_coffeescript

Make the App Pretty with Twitter Bootstrap

Bootstrap is a CSS library that makes it easy to make a web app look better. To use Bootstrap start by adding the dependency to the build.sbt file:

libraryDependencies += "org.webjars" % "bootstrap" % "2.1.1"

Note: Make sure you have an empty line before the line above.

Now restart the Play server so that it will fetch the new dependency. To use Bootstrap simply include the following line in the app/views/main.scala.html file, making sure it is between the <title> and the main.css lines:

<link rel="stylesheet" media="screen" href="@routes.Assets.at("lib/bootstrap/css/bootstrap.min.css")">

Add the following to the public/stylesheets/main.css file in order to move the main content down to a viewable location:

body {
    padding-top: 50px;
}

Create a new template component that will be used to create new Bootstrap-friendly form fields by creating a new file named app/views/twitterBootstrapInput.scala.html containing:

@(elements: helper.FieldElements)
    
<div class="control-group @if(elements.hasErrors) {error}">
    <label for="@elements.id" class="control-label">@elements.label</label>
    <div class="controls">
        @elements.input
        <span class="help-inline">@elements.errors.mkString(", ")</span>
    </div>
</div>

Update the app/views/index.scala.html file to use Bootstrap for a nice header, better layout, and nicer default fonts:

@(message: String, taskForm: Form[Task])
@implicitFieldConstructor = @{ helper.FieldConstructor(twitterBootstrapInput.render) }
    
@main("Welcome to Play") {
    
    <div class="navbar navbar-fixed-top">
        <div class="navbar-inner">
            <div class="container-fluid">
                <a id="titleLink" class="brand" href="/">@message.toUpperCase</a>
            </div>
        </div>
    </div>

    <div class="container">
        <ul id="tasks"></ul>

        @helper.form(action = routes.Application.addTask(), 'class -> "well form-horizontal") {
            @helper.inputText(taskForm("contents"), '_label -> "Contents")
            <div class="controls">
                <input type="submit" class="btn btn-primary"/>
            </div>
        }
    </div>
    
}

The template now takes a second parameter that is a Form[Task] and must be passed to the template. This will be used for helping with form validation. Update the index() method in the app/controllers/Application.java file to pass the new required parameter:

    public static Result index() {
        return ok(index.render("hello, world", play.data.Form.form(models.Task.class)));
    }

In the test/ApplicationTest.java file, update the renderTemplate test method:

        Content html = views.html.index.render("HELLO, WORLD", play.data.Form.form(models.Task.class));

Run the tests to make sure they still pass:

sbt test

Load the app in your browser and verify it still works:
http://localhost:9000/

Read more about Templates

Commit and verify your changes:

git add app/views/twitterBootstrapInput.scala.html
git commit -m "Add Bootstrap"
git diff java-bootstrap

Add Form Validation

Add a simple Required constraint to contents property on the app/models/Task.java class:

    @play.data.validation.Constraints.Required
    public String contents;

Update the addTask method on the app/controllers/Application.java controller to check for form errors and if it sees any then render the form instead of trying to save the Task:

    public static Result addTask() {
        play.data.Form<models.Task> form = play.data.Form.form(models.Task.class).bindFromRequest();
        if (form.hasErrors()) {
            return badRequest(index.render("hello, world", form));
        }
        else {
            models.Task task = form.get();
            task.save();
            return redirect(routes.Application.index());
        }
    }

Load the app in your browser verify that adding an empty Task displays an error:
http://localhost:9000/

Read more about Validation

Commit and verify your changes:

git commit -am "Add validation"
git diff java-validation

Congratulations!

You've built a Play 2.3 app and ran it locally. You've also learned how to get started with Play 2.3, Ebean, CoffeeScript, Twitter Bootstrap, jQuery, and RESTful JSON services. Have fun as you continue to learn Play 2.3!