Objectives

Here we implement an API. It comprises a set of routes (the end points), controllers for these routes together with the means of translating Java objects to and from the Json format.

Model class

Using Play framework, create a new project: myrent-service-2016.

Generate an Eclipse view of the project:

cd myrent-service-2016
play deps
play eclipsify

Launch Eclipse and open (or create) a suitable workspace.

Then, import the project.

In a models package, add this class:

package models;

import java.util.Date;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

import play.db.jpa.GenericModel;

@Entity
public class Residence extends GenericModel {

  @Id
  public Long id;
  public String geolocation;
  public Long date;
  public boolean rented;
  public String tenant;
  public double zoom;
  public String photo;

  public Residence() {
    id = unsignedLong();
    geolocation = "";
    date = 0L;
    rented = false;
    tenant = "";
    zoom = 0;
    photo = "";
  }

  public static Residence findById(Long id) {
    return find("id", id).first();
  }

  /**
   * Generate a long greater than zero
   * 
   * @return Unsigned Long value greater than zero
   */
  private Long unsignedLong() {
    long rndVal = 0;
    do {
      rndVal = new Random().nextLong();
    } while (rndVal <= 0);
    return rndVal;
  }
}

Utils - GsonBinder

Create a new package called utils. Create this class in utils.


package utils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import play.data.binding.Global;
import play.data.binding.TypeBinder;

import com.google.gson.JsonElement;
import com.google.gson.JsonParser;

@Global
public class GsonBinder implements TypeBinder<JsonElement>
{
  public Object bind(String name, Annotation[] notes, String value, Class toClass, Type toType) throws Exception
  {
    return new JsonParser().parse(value);
  }
}

ResidencesAPI

In the controllers package add a class ResidencesAPI:


package controllers;

import java.util.List;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;

import models.Residence;
import play.mvc.Controller;

public class ResidencesAPI extends Controller {

  static Gson gson = new GsonBuilder().create();

  /**
   * 
   * @param id
   * @param body
   */
  public static void createResidence(JsonElement body) {
    Residence residence = gson.fromJson(body.toString(), Residence.class);
    residence.save();
    renderJSON(gson.toJson(residence));
  }

  /**
   * This is an update and differs from createResidence in that the primary key
   * (id) is used to retrieve the original residence which is then deleted and
   * its place taken by the incoming modified residence.
   * 
   * @param body
   *          The modified residence
   */
  public static void updateResidence(JsonElement body) {
    Residence modifiedResidence = gson.fromJson(body.toString(), Residence.class);
    Residence residence = Residence.findById(modifiedResidence.id);
    if (residence != null) {
      modifiedResidence.id = residence.id;
      residence.delete();
      modifiedResidence.save();
      renderJSON(gson.toJson(modifiedResidence));
    } else {
      notFound();
    }

  }

  public static void getResidence(Long id) {
    Residence residence = Residence.findById(id);
    if (residence == null) {
      notFound();
    } else {
      renderJSON(gson.toJson(residence));
    }
  }

  public static void getResidences() {
    List<Residence> residences = Residence.findAll();
    renderJSON(gson.toJson(residences));
  }

  /**
   * 
   * @param id
   * @param residenceId
   */
  public static void deleteResidence(Long id) {
    Residence residence = Residence.findById(id);
    if (residence == null) {
      notFound();
    } else {
      residence.delete();
      renderText("success");
    }
  }

}

Configuration

Enable our in-memory database by uncommenting the usual setting in 'application.conf'

db.default=mem

Introduce the 'Bootstrap' java class directly into the 'app' folder:

import java.util.List;

import play.jobs.*;
import play.test.*;
import models.*;

@OnApplicationStart
public class Bootstrap extends Job 
{ 
  public void doJob()
  {
    if (Residence.count() == 0)
    {
     Fixtures.deleteDatabase(); 
     Fixtures.loadModels("data.yml");
    }
  }
}

Important: once this service app has been completed and tested, comment out the above doJob method before running the JUnit tests. Otherwise the tests will likely fail as the expected number of records in the database will differ from that expected in the test code.

Now, specify additional routs in config/routes file. Here is the complete version:


# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /                                       Application.index

# Residence
POST    /api/residence                                 ResidencesAPI.createResidence
GET     /api/residences                                ResidencesAPI.getResidences
GET     /api/residences/{id}                           ResidencesAPI.getResidence
DELETE  /api/residences/{id}                           ResidencesAPI.deleteResidence
POST    /api/residence/update                          ResidencesAPI.updateResidence

# Ignore favicon requests
GET     /favicon.ico                            404

# Map static resources from the /app/public folder to the /public path
GET     /public/                                staticDir:public

# Catch all
*       /{controller}/{action}                  {controller}.{action}

Provide some sample data.


Residence(residence_1):
  id: 1234
  geolocation: "52.258136,-7.127810"
  date: 1448196498688
  rented: true
  tenant: Homer
  zoom: 12.0
  photo: "photo 1"

Residence(residence_2):
  id: 1235
  geolocation: "53.258136,-7.127810"
  date: 1448196498765
  rented: true
  tenant: Barney
  zoom: 14.0
  photo: "photo 2"

Residence(residence_3):
  id: 1236
  geolocation: "52.258136,-8.127810"
  date: 1448196498999
  rented: true
  tenant: Marge
  zoom: 16.0
  photo: "photo 3"

Residence(residence_4):
  id: 1237
  geolocation: "52.258136,-7.127810"
  date: 1448196498666
  rented: true
  tenant: Lisa
  zoom: 18.0
  photo: "photo 4"

Exploring that API

The program should now build without error. Launch it and browse to the database and study the contents:


localhost:9000/@db

If you are having difficulty launching the h2 browser with the above command, use Postman.

Also, in a browser explore the urls:

http://localhost:9000/api/residences

Figure 1: http://localhost:9000/api/residences

http://localhost:9000/api/residences

Figure 2: http://localhost:9000/api/residences/1234

Postman

Launch Postman from within Chrome:

Figure 3: Postman accessible within Chrome apps

Explore the GET and DELETE HTTP commands. Here in Figure 4 is an example of the use of GET.

Figure 4: Postman - GET localhost:9000/api/residences/1234

Exercises:

Using Postman:

  1. Create a JSON version of a Residence object and POST it.

  2. Delete a Residence object.

  3. Update a Residence object.