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:
- Open a command-line window (Windows) or terminal window (Linux).
- Go to the directory where the new project to be located.
- 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.
- 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
- 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.
-
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. -
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...)
- Start either the Firefox or Chrome web browser.
-
Enter the following URL into the browser address window :
http://localhost:9000
Other than the run
command, you will
typically use the following console commands during
development:
clean
: This command deletes cached files, generated sources, and compiled classes.compile
: This command compiles the current application.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.
- Open the
project/plugins.sbt
file and add the following line:addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")
- 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.
- 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.
- From the File menu, click Import, General, and then Existing project...
- Select the root directory.
- Select the project
students-service
and click Finish.
NetBeans
Play doesn't have native NetBeans project generation support at this time. However, you can import a NetBeans project like an Eclipse project:
- In NetBeans, from the File menu, select Import Project and then Eclipse Project.
- Select Import Projects ignoring Dependencies
- In the Project to Import field enter the root of your play application.
- In Destination Folder field enter the root of your play application.
- Press Finish.
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.
- 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. - 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
- Create the
persistence.xml
file in a new folder namedMETA-INF
under theconf
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.
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.
- Open a command-line window (Windows or a terminal window (Linux).
-
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 isGET
.-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. -
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.sbt
file:
- Open the
project/plugins.sbt
file and add thesbt-assembly
dependency:addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0")
- 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.sbt
file you can configure the settings for theassembly
command.mainClass in assembly
is used to specify the class with themain
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
andassemblyOutputPath
. - 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
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
andPORT
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. Thelib
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.
-
Open the
application.conf
file located in theconf
directory. -
Add the following lines:
http.port=${?PORT} http.address=${?HOSTNAME}
- 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:
- Create an application that is configurable at runtime.
- Package your application as an uber JAR file or as separate library JAR files.
- Create an application archive that contains your
application and a the
manifest.json
file in a single file. Themanifest.json
file must stored in theroot
directory of your archive. - 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