Java SE 8: Creating a REST Service with Play and Java for Oracle Application Container Cloud Service


Options



Before You Begin

Purpose

The purpose of this tutorial is show how to build a standalone REST service using Play Framework and Java.

Time to Complete

60 minutes

Background

Usually RESTful web services are built to run in an application server. However, application servers make the infrastructure more complex. The application server is a complicated piece of software that must be properly installed and configured, and the code must be packaged and deployed, and compressed and uncompressed before it is executed. An alternative is simple Java applications packaged as JAR files that contain a main class which can be started from the command line. The JAR file contains all the necessary infrastructure (dependencies/libraries) but also an embedded HTTP server for a web application.
Deploying Java applications as a simple JAR file has been recently implemented by some technologies such as Play framework (Play).

What Is Play Framework?

Play is an open source web application framework that lets developers build simple, reusable pieces of software (micro services). Stacking those reusable micro services makes it possible to build quite complex and robust systems. Each team can concentrate on a micro service and each micro service can be tested independently. Reusable modules can be shared among developers and enterprise departments. For example, an enterprise can build its authentication module once and make it available as a module to be reused by all applications.

Scenario

You will build a simple REST application that lets you create and retrieve Student objects stored in a memory-based database (H2) using Play for java.

What Do You Need?

  • Java Development Kit 8 (JDK 8)
  • Play framework 2.4.x. This tutorial was developed using Play 2.4.0 but you can use any update of this version.
  • cURL 7.0+ cURL is installed by default on most UNIX and Linux systems. To install cURL on Windows, click here.
  • An IDE such as NetBeans or Eclipse or a programming editor.
  • student-service.zip This is the Maven project with source code. Download it if you want to view the completed project.

Creating a Play Project

Play Framework provides different commands, to create a new project, compile, package, and test your applications. The activator new command can be used to create an empty application skeleton. This command lists several application templates designed to be used  as the project's starting point. For basic Play projects, the names of these templates are play-scala for Scala-based Play applications and play-java for Java-based Play applications. To create the project:

  1. Open a command-line window (Windows) or terminal window (Linux).
  2. Go to the directory where the new project to be located.
  3. To create the new project by executing this command: activator new students-service play-java t.
    [students-service] $ activator new students-service play-java 
    C:\examples>activator new students-service play-java
    
    Fetching the latest list of templates...
    
    ago 06, 2015 4:33:18 PM com.amazonaws.http.HttpClientFactory createHttpClient
    INFO: Configuring Proxy. Proxy Host: www-proxy.us.oracle.com Proxy Port: 80
    OK, application "students-service" is being created using the "play-java" template.
    
    To run "students-service" from the command line, "cd students-service" then:
    C:\examples\students-service/activator run
    
    To run the test for "students-service" from the command line, "cd students-service" then:
    C:\examples\students-service/activator test
    
    To run the Activator UI for "students-service" from the command line, "cd students-service" then:
    C:\examples\students-service/activator ui 

Note: The first time you execute a new command, Play will need to download all the related dependencies to fulfill the command; this could take quite a while depending on your internet connection. The next time you execute the same command, Play will have what it needs, so it won't need to download anything new and will be able to execute the command more quickly.

This command creates a new Java play project named students-service and specifies that the project will support Java. After it's finished, you should see the new students-service project directory created in your current location.

Project structure
Description of this image
  • The app directory contains the application’s core, e.g. the models, controllers and views directories that will be created on demand based on project needs. This is the directory where the .java source files are located.
  • The conf directory is where you configure the application. It contains three files; the two most important files:
    • The application.conf file is where you configure things like logging, database connections, which port the server runs on, and more.
    • The routes file is where you define your application’s routes, and mappings from HTTP URLs to application code.
  • The project directory contains the files that configure the build process based on SBT (Scala Build Tool).
  • The public directory contains all the public and static assets, which includes images, style sheets, JavaScript files, and static HTML pages.
  • The test directory contains all the application tests. Tests are either written as Java JUnit tests or as Selenium tests.

Running the Application

Running your application in development mode configures your application to run in auto-reload mode, where Play will recompile any recent changes to the project files, removing the need to manually restart your application every time you make a change to the code. You are now ready to view your application using your web browser.

  1. Run the Play console, go to the project directory and execute: activator

    C:\examples>cd students-service
    
    C:\examples\students-service>activator
    [info] Loading project definition from C:\examples\students-service\project
    [info] Updating {file:/C:/examples/students-service/project/}students-service-build...
    [info] Resolving org.fusesource.jansi#jansi;1.4 ...
    [info] downloading https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/com.typesafe.play/sbt-plugin/scala_2.10/sb
    t_0.13/2.4.2/jars/sbt-plugin.jar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#sbt-plugin;2.4.2!sbt-plugin.jar (6484ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/routes-compiler_2.10/2.4.2/routes-compiler_2.10-2
    .4.2.jar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#routes-compiler_2.10;2.4.2!routes-compiler_2.10.jar (1834ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/sbt-run-support_2.10/2.4.2/sbt-run-support_2.10-2
    .4.2.jar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#sbt-run-support_2.10;2.4.2!sbt-run-support_2.10.jar (596ms)
    [info] downloading https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/com.typesafe.sbt/sbt-web/scala_2.10/sbt_0.
    13/1.2.2/jars/sbt-web.jar ...
    [info]  [SUCCESSFUL ] com.typesafe.sbt#sbt-web;1.2.2!sbt-web.jar (6276ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/build-link/2.4.2/build-link-2.4.2.jar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#build-link;2.4.2!build-link.jar (582ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-exceptions/2.4.2/play-exceptions-2.4.2.jar .
    ..
    [info]  [SUCCESSFUL ] com.typesafe.play#play-exceptions;2.4.2!play-exceptions.jar (465ms)
    [info] downloading https://repo1.maven.org/maven2/org/webjars/webjars-locator/0.26/webjars-locator-0.26.jar ...
    [info]  [SUCCESSFUL ] org.webjars#webjars-locator;0.26!webjars-locator.jar (567ms)
    [info] downloading https://repo1.maven.org/maven2/org/webjars/webjars-locator-core/0.26/webjars-locator-core-0.26.jar
     ...
    [info]  [SUCCESSFUL ] org.webjars#webjars-locator-core;0.26!webjars-locator-core.jar (561ms)
    [info] Done updating.
    [info] Set current project to students-service (in build file:/C:/examples/students-service/)
    [students-service] $ 

    Note: Your output might be a little different, but what's important is for you to see this: [students-service] $ at the end of the command.

  2. In the Play console, execute the run command:

    [students-service] $run 
    [students-service] $ run
    [info] Updating {file:/C:/examples/students-service/}root...
    [info] Resolving jline#jline;2.12.1 ...
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-server_2.11/2.4.2/play-server_2.11-2.4.2.jar
     ...
    [info]  [SUCCESSFUL ] com.typesafe.play#play-server_2.11;2.4.2!play-server_2.11.jar (1338ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-java_2.11/2.4.2/play-java_2.11-2.4.2.jar ...
    
    [info]  [SUCCESSFUL ] com.typesafe.play#play-java_2.11;2.4.2!play-java_2.11.jar (622ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-netty-server_2.11/2.4.2/play-netty-server_2.
    11-2.4.2.jar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#play-netty-server_2.11;2.4.2!play-netty-server_2.11.jar (727ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-java-jdbc_2.11/2.4.2/play-java-jdbc_2.11-2.4
    .2.jar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#play-java-jdbc_2.11;2.4.2!play-java-jdbc_2.11.jar (462ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-cache_2.11/2.4.2/play-cache_2.11-2.4.2.jar .
    ..
    [info]  [SUCCESSFUL ] com.typesafe.play#play-cache_2.11;2.4.2!play-cache_2.11.jar (570ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-java-ws_2.11/2.4.2/play-java-ws_2.11-2.4.2.j
    ar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#play-java-ws_2.11;2.4.2!play-java-ws_2.11.jar (504ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play_2.11/2.4.2/play_2.11-2.4.2.jar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#play_2.11;2.4.2!play_2.11.jar (8699ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-iteratees_2.11/2.4.2/play-iteratees_2.11-2.4
    .2.jar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#play-iteratees_2.11;2.4.2!play-iteratees_2.11.jar (2823ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-json_2.11/2.4.2/play-json_2.11-2.4.2.jar ...
    
    [info]  [SUCCESSFUL ] com.typesafe.play#play-json_2.11;2.4.2!play-json_2.11.jar (2257ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-netty-utils/2.4.2/play-netty-utils-2.4.2.jar
     ...
    [info]  [SUCCESSFUL ] com.typesafe.play#play-netty-utils;2.4.2!play-netty-utils.jar (466ms)
    [info] downloading https://repo1.maven.org/maven2/joda-time/joda-time/2.8.1/joda-time-2.8.1.jar ...
    [info]  [SUCCESSFUL ] joda-time#joda-time;2.8.1!joda-time.jar (1971ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-functional_2.11/2.4.2/play-functional_2.11-2
    .4.2.jar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#play-functional_2.11;2.4.2!play-functional_2.11.jar (1391ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-datacommons_2.11/2.4.2/play-datacommons_2.11
    -2.4.2.jar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#play-datacommons_2.11;2.4.2!play-datacommons_2.11.jar (515ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-jdbc_2.11/2.4.2/play-jdbc_2.11-2.4.2.jar ...
    
    [info]  [SUCCESSFUL ] com.typesafe.play#play-jdbc_2.11;2.4.2!play-jdbc_2.11.jar (662ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-jdbc-api_2.11/2.4.2/play-jdbc-api_2.11-2.4.2
    .jar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#play-jdbc-api_2.11;2.4.2!play-jdbc-api_2.11.jar (488ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-ws_2.11/2.4.2/play-ws_2.11-2.4.2.jar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#play-ws_2.11;2.4.2!play-ws_2.11.jar (1850ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-test_2.11/2.4.2/play-test_2.11-2.4.2.jar ...
    
    [info]  [SUCCESSFUL ] com.typesafe.play#play-test_2.11;2.4.2!play-test_2.11.jar (776ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-omnidoc_2.11/2.4.2/play-omnidoc_2.11-2.4.2.j
    ar ...
    [info]  [SUCCESSFUL ] com.typesafe.play#play-omnidoc_2.11;2.4.2!play-omnidoc_2.11.jar (16760ms)
    [info] downloading https://repo1.maven.org/maven2/com/typesafe/play/play-docs_2.11/2.4.2/play-docs_2.11-2.4.2.jar ...
    
    [info]  [SUCCESSFUL ] com.typesafe.play#play-docs_2.11;2.4.2!play-docs_2.11.jar (23438ms)
    [info] Done updating.
    
    --- (Running the application, auto-reloading is enabled) ---
    
    [info] p.a.l.c.ActorSystemProvider - Starting application default Akka system: application
    [info] p.c.s.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
    
    (Server started, use Ctrl+D to stop and go back to the console...) 
  3. Start either the Firefox or Chrome web browser.
  4. Enter the following URL into the browser address window : http://localhost:9000

    Welcome page
    Description of this image

Other than the run command, you will typically use the following console commands during development:

  1. clean: This command deletes cached files, generated sources, and compiled classes.
  2. compile: This command compiles the current application.
  3. test: This command executes unit tests and functional tests.

(Optional) Setting up Your Preferred IDE

For developing a Play application, you don't need to use a sophisticated IDE. You can edit the files with a plain text editor. Because Play compiles and refreshes the modifications you make to your source files automatically, you can easily work using a simple text editor. However, using a Java IDE such as Eclipse or IntelliJ for editing your source code is a lot easier, because they provide features like auto-completion, syntax highlighting, on-the-fly compilation, and automated refactoring and debugging.

Play provides project generation support for Eclipse, IntelliJ and ENSIME by including commands that will set up the project for you. You can also use any other IDE, but you have to set up the project in your IDE yourself.

Eclipse

This section shows you how to set up your application for Eclipse.

  1. Open the project/plugins.sbt file and add the following line: addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")
  2. Return to the Play console, stop the project bye pressing Ctrl+D and execute the command:eclipse
    [students-service] $eclipse 

    This command generates the Eclipse-specifc files that instruct Eclipse how to load your application, its dependencies and how to build it.

  3. Open Eclipse.

    After these files are generated, Eclipse will be able to recognize your application’s directory as an Eclipse project, and you can import it directly.

  4. From the File menu, click Import, General, and then Existing project...
  5. Select the root directory.
  6. Select the project students-service and click Finish.
  7. Importing project
    Description of this image

NetBeans

Play doesn't have native NetBeans project generation support at this time. However, you can import a NetBeans project like an Eclipse project:

  1. In NetBeans, from the File menu, select Import Project and then Eclipse Project.
  2. Select Import Projects ignoring Dependencies
  3. In the Project to Import field enter the root of your play application.
  4. In Destination Folder field enter the root of your play application.
  5. Press Finish.
  6. Importing Eclipse project
    Description of this image

Editing the REST Service

Enabling the Database

Play includes a built-in database called H2 and supports JPA but there is no built-in JPA implementation in Play 2.4. However, you can choose any available implementation. This tutorial uses Hibernate.

  1. Open the build.sbt file located in the project root directory and add the Hibernate and JPA dependencies:
    libraryDependencies ++= Seq(
      javaJpa,
      "org.hibernate" % "hibernate-entitymanager" % "4.3.9.Final" 
    ) 

    If you want to add a dependency on a library, you must add a line that describes it to the list of dependencies in the build.sbt file.The next time the application is started, Play will resolve the dependency, automatically download the correct JAR, and add it to the classpath.

  2. Open the conf/application.conf file and add the following lines:
    db.default.driver=org.h2.Driver
    db.default.url="jdbc:h2:mem:play"
    db.default.jndiName=DefaultDS
    jpa.default=defaultPersistenceUnit 
  3. Create the persistence.xml file in a new folder named META-INF under the conf directory, and add the following code:
    <persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
                 version="2.1">
    
        <persistence-unit name="defaultPersistenceUnit" transaction-type="RESOURCE_LOCAL">
            <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
            <non-jta-data-source>DefaultDS</non-jta-data-source>
            <properties>
                <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
                <property name="hibernate.hbm2ddl.auto" value="update" />
            </properties>
        </persistence-unit>
    
    </persistence>

Creating the Student Domain Class

Create a new package name models under the app directory and then create the Student class.

package models;

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

@Entity
public class Student  {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;
	private String name;
	private String lastName;
	
	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}				
}	                        

The Student class is a simple POJO with fields, setters, and getters that represents the Student table in the database.

@Entity declares the class as a persistent POJO class.

@Id declares the identifier property of this entity.

@GeneratedValue specifies that a value will be automatically generated for the id field.

Note: If you are using Eclipse and you are getting compilation errors in the IDE, then execute the eclipse command again.

Adding a Controller and Actions

The controller in Play handles all the requests and responses between the client and web server. It is a plain Java class that extends theController class of Play and is the home for action methods, which are the entry points of your web application.

Create the StudentController class in the controllers package.

/* Copyright © 2015 Oracle and/or its affiliates. All rights reserved. */
package controllers;

import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import models.Student;

import play.db.jpa.JPA;
import play.db.jpa.Transactional;
import play.libs.Json;
import play.mvc.BodyParser;
import play.mvc.Controller;
import play.mvc.Result;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import static play.libs.Json.toJson;

public class StudentController extends Controller {

	@Transactional
	@BodyParser.Of(BodyParser.Json.class)
	public Result addStudent() {
		ObjectMapper mapper = new ObjectMapper();
		try {
			JsonNode json = request().body().asJson();
			Student newStudent = mapper.readValue(json.toString(),
					Student.class);
			JPA.em().persist(newStudent);
			ObjectNode result = Json.newObject();
			result.put("Student", Json.toJson(newStudent));
			return created(result);
		} catch (Exception e) {
			e.printStackTrace();
			return badRequest("Missing information");
		}

	}

	@Transactional(readOnly = true)
	public  Result listStudents() {
		CriteriaBuilder cb = JPA.em().getCriteriaBuilder();
		CriteriaQuery<Student> cq = cb.createQuery(Student.class);
		Root<Student> root = cq.from(Student.class);
		CriteriaQuery<Student> all = cq.select(root);
		TypedQuery<Student> allQuery = JPA.em().createQuery(all);
		JsonNode jsonNodes = toJson(allQuery.getResultList());
		return ok(jsonNodes);
	}
}

This controller contains two methods or actions: addStudent to create a new record to the student's table and listStudents to retrieve all the students.

Play automatically manages transactions for you. It starts a transaction for each HTTP request and commits it when the HTTP response is sent. To enable a JPA transaction for a particular action you must annotate it with @play.db.jpa.Transactional. If your action runs only queries, then you can set the readOnly attribute to true.

@BodyParser is used to specify the type of parser Play will use to transform the body into a Java value. In this case, you will use JSON format but Play supports other formats such as plain text, multipart, formURLEnconded, and XML.

In the listStudents action you will use a CriteriaQuery but you can also use a NameQuery.

Defining the Applications Routes

The router is the component that translates each incoming HTTP request to an action call (a public method in a controller class).

Routes are defined in the conf/routes file, and any route consists of three parts: the HTTP method, the path, and the action method.

In this section, you define two routes for the two new actions in the StudentController: addStudent and listStudents.

Open the conf/routes file and add the two new routes:
POST    /students/add           controllers.StudentController.addStudent()
GET     /students/all		controllers.StudentController.listStudents()

Testing the Application

To test your application, you can use any REST client that you want. The following examples use the curl tool.

Note: Make sure that you save all your files and that your application is running.

  1. Open a command-line window (Windows or a terminal window (Linux).
  2. Add a new record:

    curl -X POST -H "Content-Type: application/json"  -d "{\"name\" : \"Robert\", \"lastName\" : \"Tomson\"}" http://localhost:9000/students/add
    • -X specifies the request method to use when communicating with the HTTP server. The specified request is used instead of the default method, which is GET.
    • -H "Content-Type:application/json" sets the content type so the application knows the payload contains a JSON object.
    • -d is used to send data.
    C:\Program Files\curl>curl -X POST -H "Content-Type: application/json"  -d "{\"name\" : \"Robert\", \"lastName\" : \"
    Tomson\"}" http://localhost:9000/students/add
    {"Student":{"id":1,"name":"Robert","lastName":"Tomson"}}

    You can see the output contains the id generated for the new student created.

  3. Retrieve all the records: curl -X GET http://localhost:9000/students/all

    C:\Program Files\curl>curl -X GET http://localhost:9000/students/all
    [{"id":1,"name":"Robert","lastName":"Tomson"}]
    

Creating a Distribution Artifact

In development mode, the run command can be used to execute a Play application, however the run command should not be used to run an application in production mode. There are several ways to deploy a Play application in production mode. This section shows you two ways to create a distribution artifact of your Play application.

Using the dist Command

The dist command builds a binary version of your application that you can deploy to a server without any dependency on sbt or activator. The only thing the server needs is a Java installation.

In the Play console, enter the dist command:

[students-service] $ dist 
[students-service] $ dist
[info] Wrote C:\examples\students-service\target\scala-2.11\students-service_2.11-1.0-SNAPSHOT.pom
[info] Main Scala API documentation to C:\examples\students-service\target\scala-2.11\api...
[info] Packaging C:\examples\students-service\target\scala-2.11\students-service_2.11-1.0-SNAPSHOT.jar ...
[info] Done packaging.
model contains 25 documentable templates
[info] Main Scala API documentation successful.
[info] Packaging C:\examples\students-service\target\scala-2.11\students-service_2.11-1.0-SNAPSHOT-javadoc.jar ...
[info] Done packaging.
[info] Packaging C:\examples\students-service\target\scala-2.11\students-service_2.11-1.0-SNAPSHOT-sans-externalized.
jar ...
[info] Done packaging.
[info]
[info] Your package is ready in C:\examples\students-service\target\universal\students-service-1.0-SNAPSHOT.zip
[info]
[success] Total time: 10 s, completed 6/08/2015 04:46:17 PM
[students-service] $ 

This command produces a ZIP file that  contains all the JAR files needed to run your application in the target/universal folder of your application.

To run the application, you must uncompress the file on the target server, and then run the script in the bin directory. In the bin directory you will see two files: a bash shell script and a Windows .bat script.

Note: The generated start script causes an error on some platforms (Windows) when the classpath is too big. A workaround is to modify the classpath in the script file to:

set "APP_CLASSPATH=%APP-LIB-DIR%\..\conf;%APP_LIB_DIR%\\*" 

Replace the list of JARS files using the asterisk (*) wildcard.

Using the sbt Assembly Plugin

The sbt assembly plugin can be used to package and run Play applications. This will produce one JAR file as an output artifact and lets you execute it directly using the Java command. To use this command, you must add a dependency in the plugin.sbt file and add the settings in the build.sbtfile:

  1. Open the project/plugins.sbt file and add the sbt-assembly dependency:
    addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0")
  2. Open the build.sbt file and add the following lines:
    mainClass in assembly := Some("play.core.server.NettyServer")
    
    // Exclude commons-logging because it conflicts with the jcl-over-slf4j
    libraryDependencies ~= { _ map {
      case m if m.organization == "com.typesafe.play" =>
        m.exclude("commons-logging", "commons-logging")
      case m => m
    }}
    
    // Take the first ServerWithStop because it's packaged into two jars
    assemblyMergeStrategy in assembly := {
      case x if Assembly.isConfigFile(x) => MergeStrategy.concat
      case PathList(ps @ _*) if Assembly.isReadme(ps.last) || Assembly.isLicenseFile(ps.last) => MergeStrategy.rename
      case PathList("META-INF", xs @ _*) => (xs map {_.toLowerCase}) match {
    	  case ("manifest.mf" :: Nil) | ("index.list" :: Nil) | ("dependencies" :: Nil) => MergeStrategy.discard
          case ps @ (x :: xs) if ps.last.endsWith(".sf") || ps.last.endsWith(".dsa") => MergeStrategy.discard
          case "plexus" :: xs => MergeStrategy.discard
          case "spring.tooling" :: xs => MergeStrategy.discard
          case "services" :: xs => MergeStrategy.filterDistinctLines
          case ("spring.schemas" :: Nil) | ("spring.handlers" :: Nil) => MergeStrategy.filterDistinctLines
          case _ => MergeStrategy.deduplicate
        }
      case "asm-license.txt" | "overview.html" => MergeStrategy.discard
    
      // Take the first ServerWithStop because it's packaged into two jars
      case "play/core/server/ServerWithStop.class" => MergeStrategy.first
      case PathList("javax", "annotation", "Syntax.class") => MergeStrategy.first
      case PathList("javax", "annotation", "Syntax.java") => MergeStrategy.first
      case PathList("javax", "annotation", "meta","When.class") => MergeStrategy.first
      case PathList("javax", "transaction",xs @ _*) => MergeStrategy.first  
      case other => (assemblyMergeStrategy in assembly).value(other)
    } 

    In the build.sbtfile you can configure the settings for the assembly command.

    mainClass in assembly is used to specify the class with the main method, which is the entry point when the JAR file is executed.

    libraryDependencies is used to add or exclude JAR files in the classpath because of the merge conflicts.

    assemblyMergeStrategy in assembly is used to configure the merge strategy when multiple files with the same name share the same relative path.

    There are others properties you can set up for the assembly command, such asassemblyJarName and assemblyOutputPath.

  3. In the Play console, execute: assembly
    [students-service] $assembly 
    [students-service] $ assembly
    [info] Compiling 2 Java sources to C:\examples\students-service\target\scala-2.11\test-classes...
    [info] Including: HikariCP-2.3.7.jar
    [info] Including: snakeyaml-1.15.jar
    [info] Including: jackson-databind-2.5.4.jar
    [info] Including: scala-library-2.11.6.jar
    [info] Including: h2-1.4.187.jar
    [info] C:\examples\students-service\test\ApplicationTest.java: C:\examples\students-service\test\ApplicationTest.java
     uses or overrides a deprecated API.
    [info] C:\examples\students-service\test\ApplicationTest.java: Recompile with -Xlint:deprecation for details.
    [info] Including: hibernate-validator-5.0.3.Final.jar
    [info] Including: jackson-datatype-jdk8-2.5.4.jar
    [info] Including: jackson-datatype-jsr310-2.5.4.jar
    [info] Including: play-netty-utils-2.4.1.jar
    [info] Including: slf4j-api-1.7.12.jar
    [info] Including: validation-api-1.1.0.Final.jar
    [info] Including: jul-to-slf4j-1.7.12.jar
    [info] Including: tyrex-1.0.1.jar
    [info] Including: jcl-over-slf4j-1.7.12.jar
    [info] Including: scala-parser-combinators_2.11-1.0.1.jar
    [info] Including: classmate-1.0.0.jar
    [info] Including: jboss-logging-3.2.1.Final.jar
    [info] Including: play-cache_2.11-2.4.1.jar
    [info] Including: spring-context-4.1.6.RELEASE.jar
    [info] Including: ehcache-core-2.6.11.jar
    [info] Including: logback-core-1.1.3.jar
    [info] - application - Creating Pool for datasource 'default'
    [info] Including: logback-classic-1.1.3.jar
    [info] - play.api.db.HikariCPConnectionPool - datasource [jdbc:h2:mem:play-test-1179263940;] bound to JNDI as Default
    DS
    [info] Including: akka-actor_2.11-2.3.11.jar
    [info] Including: spring-core-4.1.6.RELEASE.jar
    [info] Including: play-java-ws_2.11-2.4.1.jar
    [info] Including: play-ws_2.11-2.4.1.jar
    [info] - play.api.libs.concurrent.ActorSystemProvider - Starting application default Akka system: application
    [info] Including: async-http-client-1.9.21.jar
    [info] Including: spring-beans-4.1.6.RELEASE.jar
    [info] Including: reflections-0.9.9.jar
    [info] Including: signpost-core-1.2.1.2.jar
    [info] Including: signpost-commonshttp4-1.2.1.2.jar
    [info] Including: guava-18.0.jar
    [info] Including: httpcore-4.0.1.jar
    [info] Including: httpclient-4.0.1.jar
    [info] - play.api.libs.concurrent.ActorSystemProvider - Shutdown application default Akka system: application
    [info] - application - Shutting down connection pool.
    [info] Including: akka-slf4j_2.11-2.3.11.jar
    [info] Including: commons-codec-1.10.jar
    [info] Passed: Total 3, Failed 0, Errors 0, Passed 3
    [info] Including: play-java-jpa_2.11-2.4.1.jar
    [info] Including: xercesImpl-2.11.0.jar
    [info] Including: hibernate-jpa-2.1-api-1.0.0.Final.jar
    [info] Including: twirl-api_2.11-1.1.1.jar
    [info] Including: hibernate-entitymanager-4.3.9.Final.jar
    [info] Including: commons-lang3-3.4.jar
    [info] Including: scala-xml_2.11-1.0.1.jar
    [info] Including: jboss-logging-annotations-1.2.0.Beta1.jar
    [info] Including: hibernate-core-4.3.9.Final.jar
    [info] Including: annotations-2.0.1.jar
    [info] Including: typetools-0.4.1.jar
    [info] Including: play-enhancer-1.1.0.jar
    [info] Including: jsr305-2.0.3.jar
    [info] Including: play-server_2.11-2.4.1.jar
    [info] Including: xml-apis-1.4.01.jar
    [info] Including: tomcat-servlet-api-8.0.21.jar
    [info] Including: play-netty-server_2.11-2.4.1.jar
    [info] Including: play_2.11-2.4.1.jar
    [info] Including: netty-3.10.3.Final.jar
    [info] Including: jta-1.1.jar
    [info] Including: guice-4.0.jar
    [info] Including: javax.inject-1.jar
    [info] Including: aopalliance-1.0.jar
    [info] Including: guice-assistedinject-4.0.jar
    [info] Including: play-java_2.11-2.4.1.jar
    [info] Including: scala-java8-compat_2.11-0.3.0.jar
    [info] Including: play-iteratees_2.11-2.4.1.jar
    [info] Including: netty-http-pipelining-1.1.4.jar
    [info] Including: play-java-jdbc_2.11-2.4.1.jar
    [info] Including: play-jdbc_2.11-2.4.1.jar
    [info] Including: play-jdbc-api_2.11-2.4.1.jar
    [info] Including: bonecp-0.8.0.RELEASE.jar
    [info] Including: scala-stm_2.11-0.7.jar
    [info] Including: config-1.3.0.jar
    [info] Including: play-json_2.11-2.4.1.jar
    [info] Including: play-functional_2.11-2.4.1.jar
    [info] Including: play-datacommons_2.11-2.4.1.jar
    [info] Including: joda-time-2.7.jar
    [info] Including: build-link-2.4.1.jar
    [info] Including: play-exceptions-2.4.1.jar
    [info] Including: javassist-3.19.0-GA.jar
    [info] Including: joda-convert-1.7.jar
    [info] Including: jboss-transaction-api_1.2_spec-1.0.0.Final.jar
    [info] Including: dom4j-1.6.1.jar
    [info] Including: scala-reflect-2.11.6.jar
    [info] Including: hibernate-commons-annotations-4.0.5.Final.jar
    [info] Including: antlr-2.7.7.jar
    [info] Including: jandex-1.1.0.Final.jar
    [info] Including: jackson-core-2.5.4.jar
    [info] Including: jackson-annotations-2.5.4.jar
    [info] Checking every *.class/*.jar file's SHA-1.
    [info] Merging files...
    [warn] Merging 'NOTICE' with strategy 'rename'
    [warn] Merging 'META-INF\NOTICE.txt' with strategy 'rename'
    [warn] Merging 'license\LICENSE' with strategy 'rename'
    [warn] Merging 'README' with strategy 'rename'
    [warn] Merging 'META-INF\NOTICE' with strategy 'rename'
    [warn] Merging 'license\NOTICE' with strategy 'rename'
    [warn] Merging 'META-INF\LICENSE.txt' with strategy 'rename'
    [warn] Merging 'license' with strategy 'rename'
    [warn] Merging 'META-INF\notice.txt' with strategy 'rename'
    [warn] Merging 'META-INF\license' with strategy 'rename'
    [warn] Merging 'META-INF\README' with strategy 'rename'
    [warn] Merging 'META-INF\license.txt' with strategy 'rename'
    [warn] Merging 'META-INF\LICENSE' with strategy 'rename'
    [warn] Merging 'LICENSE' with strategy 'rename'
    [warn] Merging 'META-INF\DEPENDENCIES' with strategy 'discard'
    [warn] Merging 'META-INF\INDEX.LIST' with strategy 'discard'
    [warn] Merging 'META-INF\MANIFEST.MF' with strategy 'discard'
    [warn] Merging 'META-INF\services\com.fasterxml.jackson.databind.Module' with strategy 'filterDistinctLines'
    [warn] Merging 'META-INF\spring.handlers' with strategy 'filterDistinctLines'
    [warn] Merging 'META-INF\spring.schemas' with strategy 'filterDistinctLines'
    [warn] Merging 'META-INF\spring.tooling' with strategy 'discard'
    [warn] Merging 'javax\annotation\Syntax.class' with strategy 'first'
    [warn] Merging 'javax\annotation\Syntax.java' with strategy 'first'
    [warn] Merging 'javax\annotation\meta\When.class' with strategy 'first'
    [warn] Merging 'javax\transaction\HeuristicCommitException.class' with strategy 'first'
    [warn] Merging 'javax\transaction\HeuristicMixedException.class' with strategy 'first'
    [warn] Merging 'javax\transaction\HeuristicRollbackException.class' with strategy 'first'
    [warn] Merging 'javax\transaction\InvalidTransactionException.class' with strategy 'first'
    [warn] Merging 'javax\transaction\NotSupportedException.class' with strategy 'first'
    [warn] Merging 'javax\transaction\RollbackException.class' with strategy 'first'
    [warn] Merging 'javax\transaction\Status.class' with strategy 'first'
    [warn] Merging 'javax\transaction\Synchronization.class' with strategy 'first'
    [warn] Merging 'javax\transaction\SystemException.class' with strategy 'first'
    [warn] Merging 'javax\transaction\Transaction.class' with strategy 'first'
    [warn] Merging 'javax\transaction\TransactionManager.class' with strategy 'first'
    [warn] Merging 'javax\transaction\TransactionRequiredException.class' with strategy 'first'
    [warn] Merging 'javax\transaction\TransactionRolledbackException.class' with strategy 'first'
    [warn] Merging 'javax\transaction\TransactionSynchronizationRegistry.class' with strategy 'first'
    [warn] Merging 'javax\transaction\UserTransaction.class' with strategy 'first'
    [warn] Merging 'javax\transaction\xa\XAException.class' with strategy 'first'
    [warn] Merging 'javax\transaction\xa\XAResource.class' with strategy 'first'
    [warn] Merging 'javax\transaction\xa\Xid.class' with strategy 'first'
    [warn] Merging 'reference.conf' with strategy 'concat'
    [warn] Strategy 'concat' was applied to a file
    [info] Strategy 'deduplicate' was applied to 61 files (Run the task at debug level to see details)
    [warn] Strategy 'discard' was applied to 4 files
    [warn] Strategy 'filterDistinctLines' was applied to 3 files
    [warn] Strategy 'first' was applied to 21 files
    [warn] Strategy 'rename' was applied to 14 files
    [info] SHA-1: ec52933c79179e8efa342bef31bdc4029257f482
    [info] Packaging C:\examples\students-service\target\scala-2.11\students-service-assembly-1.0-SNAPSHOT.jar ...
    [info] Done packaging.
    [success] Total time: 123 s, completed 6/08/2015 04:50:39 PM 
  4. The assembly command generates a JAR file in the target/scala-2.11 directory. You should see a file with the following or a similar name: students-service-assembly-1.0-SNAPSHOT.jar.

    To execute the JAR file, open a new command-line window and enter: java -jar students-service-assembly-1.0-SNAPSHOT.jar.

Developing an Application for Oracle Cloud Deployment

To properly package, deploy, and execute your application in Oracle Application Container Cloud Service there are a few key concepts you must understand.

Container Considerations

Oracle Application Container Cloud Service applications are run in Docker containers. Docker is an open source software container project. Think of a container as of a very lightweight virtual machine. Your application runs in its own isolated execution space that has its own memory, file system, and network access. Access to these operating system resources takes place without the cost of implementing an entire virtual operating system.

When you deploy your application to Oracle Application Container Cloud Service, a container is dynamically generated for your application. The implication of this is that all the typical configuration information like the host name and port number are also dynamically generated.

Note: Any application that runs in this environment must be read key configuration information from the environment before the application starts.

Additionally, containers for your application can be dynamically allocated to scale up or down as is required by the application load. This dynamic scaling feature is made possible by the load balancers provided by Oracle Application Container Cloud Service. The balancer routes traffic to the application instances, balancing the load.

Given the setup of the service, this has implications for your applications.

Application Requirements

For a Java application to run on Oracle Application Container Cloud Service, there are a few requirements that must be met for the application to execute properly. The following is a list of architectural properties you application must have.

  • Applications must be stateless: Because the service can run multiple instances of the same application, applications cannot share state. For example, items added to a shopping cart application on one instance would not be available to other instances of the application. Because application load is balanced between instances, there is no guarantee that the next request would be handled by the same instance as the last request. Trying to store an application state could result in possible data loss. Therefore, state information should be stored outside your application in a database. Modern databases are designed to handle connections from multiple concurrent clients.
  • Applications communicate via network ports: The only way to communicate to a running application is through its assigned network port.
  • Application must be configurable at runtime: Applications must be able to read their HOSTNAME and PORT using environment variables. This data can only be determined when an application launches in its host container. The application must be able to read these variables from the environment.
  • All dependencies must be included with the application: If your application requires a library to execute, then that library must be included with the application when it is deployed. This can be accomplished in two ways.
    • Create an Uber JAR: When creating your application, all dependent libraries are included in the JAR with the application.
    • Use the classpath: All dependent libaries are included in separate JAR files, but the path to each file is included in the CLASSPATH passed to your application.

If you follow the architecture principles described above, you application should work fine on Oracle Application Container Cloud Service. For more information, See the Developer Guide.

Creating a Cloud-Ready Package Using the manifest.json File

The final step to package your application is combine your application archive with the manifest.json file.

The manifest.json File

The manifest.json file provides metadata about your Java application. This information includes the version of Java and the command used to execute the application. Notes about your application along with release information may be included.

{
    "runtime": {
        "majorVersion": "7"
    },
    "command": "sh target\/bin\/start",
    "release": {
        "build": "150520.1154",
        "commit": "d8c2596364d9584050461",
        "version": "15.1.0"
    },
    "notes": "notes related to release"
}

JSON Fields Defined

  • runtime
    • majorVersion: The version of the runtime for example, for Java is 7 or 8
  • command: The command to execute after deploying the application
  • release
    • build: A user-specified text value that identifies this build
    • commit:   A user-specified text value related to this deployment
    • version: The version of the text string maintained by the user

Here is the manifest.json file used in the sample application.

{
    "runtime":{
        "majorVersion": "8"
    },
    "command": "java -jar students-service-assembly-1.0-SNAPSHOT.jar",
    "release": {
        "build": "20150813Build",
        "commit": "commitString",
        "version": "20150813Version"
    },
    "notes": "REST app for testing"
}

Note: This is an uber JAR deployment given the simplicity of the command to launch the application. This application also uses Java version 8. If your application needs any special command line switches to run, this is where you would specify them.

Creating the Final Archive

As a final step, you create an archive that contains the application archive and the manifest.json file. Both files are stored at the root level of the archive file.

To create an archive using the zip command.

zip students-play-service.zip manifest.json students-service-assembly-1.0-SNAPSHOT.jar

To create an archive using the tar command.

tar cvfz students-play-service.tgz manifest.json students-service-assembly-1.0-SNAPSHOT.jar

After creating the archive, your application should be ready for deployment.

Packaging an Application Archive Without an Uber JAR File

As an alternative to an uber JAR file, an application can be started by specifying libraries on the Java command line or by using a bash shell script. Oracle Application Container Cloud Service uses a Linux container for the execution of applications so most of the rules that apply to running a command in Linux will apply.

Assumptions

The two examples that follow are based on the following assumptions:

  • The home directory for the application is /u01/app.
  • The application execution code is stored in a JAR file named app.jar.
  • All required Java libraries are stored in the lib directory. The lib directory will be included as part of the final archive as a subdirectory of the archive root directory.
  • The lib directory contains the following JAR libraries: web.jar, rest.jar, media.jar.

Executing the Application with the Java and Classpath

Given the previous assumptions and assuming the lib directory is included as described, the following command could be used to launch the application:

java -cp '/u01/app/lib/*' -jar app.jar

After, the application is deployed, the libraries in the lib directory are copied under the application's home directory. The previous command executes the java command and loads all of the libraries in the /u01/app/lib directory. This should provide the JVM with all the necessary classes to run the application.

Executing the Application with a Shell Script

As an alternative, you could execute your application using a shell script. The command line for executing your application should look something like this:

bash -l ./applicationScript.sh

The CLASSPATH environment variable can also be used to set the path. In this example, the script could contain the following two lines:

export CLASSPATH=/u01/app/lib/web.jar;/u01/app/lib/rest.jar;/u01/app/lib/media.jar;
java -jar app.jar

Configuring Your Application to Run in the Cloud

For a Java SE application to run in Oracle Application Container Cloud Service, the application must be able to read settings from environment variables set in the application's container. Specifically, your application needs to read the HOSTNAME and PORT environment variables, you must configure your application accordingly.

  1. Open the application.conf file located in the conf directory.

  2. Add the following lines:

    http.port=${?PORT}
    http.address=${?HOSTNAME}
  3. Regenerate the ditribution artifact.

With these improvements, you can set the host name or port using environment variables.

Downloading the Updated Suggested Solution

If you want to see the complete set of project files, the download the source files and Maven project from the following link: students-service.zip.

Cloud Deployment Summary

To properly package your application, you must follow these steps:

  1. Create an application that is configurable at runtime.
  2. Package your application as an uber JAR file or as separate library JAR files.
  3. Create an application archive that contains your application and a the manifest.json file in a single file. The manifest.json file must stored in the root directory of your archive.
  4. Deploy your application to Oracle Application Container Cloud Service.

Note: The manifest.json file is optional because the information in the file can also be configured using the user interface.

Deploying Your Applicaiton to Oracle Application Container Cloud Service

Now that you have created an application and packaged it for deployment on Oracle Application Container Cloud Service, you are ready to deploy it. The steps for deploying your application are described in the OBE: Deploying an Application to Oracle Application Container Cloud Service.

Want to Learn More?

Credits

  • Curriculum Developer: Luz Elena Peralta Ayala
  • QA: Drishya TM