Before You Begin
Purpose
This tutorial shows you how to develop and package a Grizzly/Jersey Java Platform, Standard Edition (Java SE) REST application so that you can deploy it to Oracle Application Container Cloud Service.
Time to Complete
60 minutes
Background
Typically, when Java developers think about creating a RESTful web service, they assume that they must use a Java Platform, Enterprise Edition (Java EE) application server. However, Java SE offers simpler, lightweight alternative methods for creating RESTful applications. This tutorial demonstrates one such alternative: Using the Grizzly web server and the Jersey REST framework (Jersey).
This tutorial covers the basics of creating a RESTful Grizzly/Jersey application with Maven. The application includes a simple data model to demonstrate how actual REST requests are processed. You create a web service class with annotations that convert your methods into callable REST URLs. Finally, you package your Java REST application into a single portable Java Archive (JAR) file, which you can execute most anywhere.
Grizzly Web Server
Project Grizzly is a pure Java web service built using the NIO API. Grizzly's main use case is the web server component for the GlassFish application server. However, Grizzly’s goal is to help you do much more than that. With Grizzly, you can build scalable and robust servers by using NIO and extended framework components, such as Web Framework (HTTP/S), WebSocket, and Comet.
Jersey, REST, and JAX-RS
Jersey is an open source, production-quality framework for developing RESTful web services in Java. Jersey provides support for JAX-RS APIs and serves as a JAX-RS reference implementation. Jersey helps to expose your data in a variety of representation media types and abstracts away the low-level details of the client/server communication. In Java, Jersey simplifies development of RESTful web services and their clients in a standard and portable way.
Representational State Transfer (REST) is a software architecture style for creating scalable web services. REST, a simpler alternative than Simple Object Access Protocol (SOAP) and Web Services Definition Language (WSDL) web services, is very popular. RESTful systems communicate through HTTP and use the same verbs (such as GET, POST, PUT, and DELETE) that web browsers use to retrieve web pages and send data to remote servers.
The JAX-RS API provides portable APIs for developing, exposing, and accessing web applications designed and implemented in compliance with principles of REST architectural style. Jersey is the reference implementation of JAX-RS.
Scenario
The RESTful web service built in this tutorial is the start of a REST application for managing customer data. For this example, customer data is stored in a list. You create two methods: One returns all customers stored in the list, and the other searches for a customer by ID. To keep things simple, all data is returned in plain text format.
Context
This OBE covers developing and packaging your application. To deploy your application, see Deploying an Application to Oracle Application Container Cloud Service.
What Do You Need?
- Maven 3.3.1 or later
- Java SE 8u45 or later
- An integrated development environment (IDE), like NetBeans or Eclipse, or a programming editor
- cURL command-line tool
- Cygwin (Windows only: Select cURL during the installation.)
- Google Chrome 46 or later
Creating the Maven Project
Installing an Archetype
When you create a Grizzly/Jersey application, your first step is to create a Maven project. An archetype exists for this type of application.
-
Navigate to the directory where you want to create a Grizzly/Jersey project (for example,
c:\examples).
-
Create an empty project:
View Codemvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 -DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false -DgroupId=com.example.rest -DartifactId=jersey-service -Dpackage=com.example.rest -DarchetypeVersion=2.17
Notes:
- If you're using a proxy server, configure Maven, and then create the empty project.
- The first time you run Maven, it takes a few minutes to cache dependencies.
You can change the various options in the command. The
archetypeArtifactId
specifies the kind of project to create. ThearchetypeGroupId
specifies which group this archetype is defined in. ThegroupId
uniquely identifies the project, should be based on your domain name, and typically matches your package name. TheartifactId
is the name of your project and specifies the name of the project archive files, such as JAR or Web Archive (WAR).The result of the command is a
jersey-service
directory whose project structure looks like this:View ImageDescription of this image Note: You can open the project in NetBeans (select File, and then Open project) or in Eclipse (select File, then Import, then Maven, and then Existing Maven Project), but you must set up Maven correctly in your IDE.
Examining the pom.xml
File
When you created the main project, you also
created the main configuration file, pom.xml.
This file specifies dependencies and versions
required to build the project, as described in
this section.
Note: If you want to view the entire file, click this link: pom.xml.
Project Section: Project Metadata Specified on the Command Line
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.rest</groupId>
<artifactId>jersey-service</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>jersey-service</name>
The groupId
and artifactId
are specified here. The name
tag
identifies the name of the project. The artifactId
is used to create the JAR or WAR file name and
the text in the version
tag. For
example, this project creates a JAR file named jersey-service-1.0-SNAPSHOT.jar.
Dependencies Section: Code Dependencies Required for the Project
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
</dependency>
<!-- uncomment this to get JSON support:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
</dependency>
-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
</dependencies>
The dependencyManagement
tag
contains libraries that other dependencies
depend on. In this case, the Jersey REST library
is specified. The first dependency provides
containers for loading Jersey classes in the
Grizzly web server. The second dependency
specifies the JUnit library for unit testing.
Build Section: Plug-Ins Used by the Project
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<inherited>true</inherited>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.example.rest.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<jersey.version>2.17</jersey.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
The build
section of the file
contains information about plug-ins required to
build the application. The maven-compiler-plugin
compiles code for the project. The default Java
version is 1.7.
Note: To use Lambda expressions in the application, change this value to 1.8, and then save the file.
The exec-maven-plugin
specifies
how to execute the application. Notice the
reference to the Main
class used
to execute the application. You can specify
application-wide properties in the properties
tag near the end of the file.
Examining the Source Files
Two source files are created for this archetype.
The Main
class (Main.java)
starts the application execution by setting up
the web server. If Jersey annotated classes are
found in the com.example.rest
package, they're loaded.
package com.example.rest;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import java.io.IOException;
import java.net.URI;
public class Main {
// Base URI the Grizzly HTTP server will listen on
public static final String BASE_URI = "http://localhost:8080/myapp/";
public static HttpServer startServer() {
// create a resource config that scans for JAX-RS resources and providers
// in com.example.rest package
final ResourceConfig rc = new ResourceConfig().packages("com.example.rest");
// create and start a new instance of grizzly http server
// exposing the Jersey application at BASE_URI
return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
}
public static void main(String[] args) throws IOException {
final HttpServer server = startServer();
System.out.println(String.format("Jersey app started with WADL available at "
+ "%sapplication.wadl\nHit enter to stop it...", BASE_URI));
System.in.read();
server.stop();
}
}
The BASE_URI
field specifies
where your defined REST method is appended. In
this case, any methods created are listed under
myapp
. The ResourceConfig
class specifies which package contains annotated
Jersey classes to be loaded. In the main
method, the HTTP server is started and run until
you press Enter.
The MyResource
class (MyResource.java)
defines the methods for a web service.
package com.example.rest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
/* Root resource (exposed at "myresource" path) */
@Path("myresource")
public class MyResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getIt() {
return "Got it!";
}
}
The class contains only a getIt
method. When this method is called, a text
message (Got it!)
is returned. The
method is called when a GET
method
is made against the http://localhost:8080/myapp/myresource
URL.
Running the Web Service
With the project set up, you can use Maven to run the application and start the web server. In this example, use Google Chrome to test the web service.
-
Open a Terminal or Command Prompt window.
-
Change the project directory:
cd C:\examples\jersey-service.
-
Compile the project:
mvn clean compile.
-
Execute the project:
mvn exec:java.
The output produced from this command may vary, especially the first time you run the command. However, if you look at the end of the output, you should see an indication that the Grizzly web service is running with the Jersey REST web service. It should look similar to the following:
View Text
C:\examples\jersey-service>mvn exec:java
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building jersey-service 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) > validate @ jersey-servic
e >>>
[INFO]
[INFO]
[INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ jersey-service ---
Apr 30, 2015 5:26:05 PM org.glassfish.grizzly.http.server.NetworkListener start
INFO: Started listener bound to [localhost:8080]
Apr 30, 2015 5:26:05 PM org.glassfish.grizzly.http.server.HttpServer start
INFO: [HttpServer] Started.
Jersey app started with WADL available at http://localhost:8080/myapp/applicatio
n.wadl
Press Enter to stop it... -
Open a Chrome web browser to test the web service.
-
In the address bar, enter
http://localhost:8080/myapp/myresource.
The output in your browser should look similar to the following image. Notice that "Got it!" appears in the browser.
View ImageDescription of this image
Creating a Customer Web Service
In this section, you create the web service components: Add sample customer data to the project and modify the source files to look up an individual customer or a complete list of customers.
Adding Customer Data
The customer data consists of two classes. The
Customer.java
class uses the
builder pattern to represent customer data. The
CustomerList.java
class creates a
list of customers using sample data.
-
Add the complete Customer.java class to the project.
The following code fragment is from the
Customer.java
file. It shows the fields used in the customer class. Along
is used to store the ID field. The other fields are strings.View Code
public class Customer {
private final long id;
private final String firstName;
private final String lastName;
private final String email;
private final String city;
private final String state;
private final String birthday; -
Add the complete CustomerList.java class to the project.
The following code fragment is from the
CustomerList.java
file. It shows three of the five customers used in the customer list. Each customer is added by using a fluent approach.View Code
static {
// Create list of customers
cList.add(
new Customer.CustomerBuilder().id()
.firstName("George")
.lastName("Washington")
.email("gwash@example.com")
.city("Mt Vernon")
.state("VA")
.birthday("1732-02-23")
.build()
);
cList.add(
new Customer.CustomerBuilder().id()
.firstName("John")
.lastName("Adams")
.email("jadams@example.com")
.city("Braintree")
.state("MA")
.birthday("1735-10-30")
.build()
);
cList.add(
new Customer.CustomerBuilder().id()
.firstName("Thomas")
.lastName("Jefferson")
.email("tjeff@example.com")
.city("CharlottesVille")
.state("VA")
.birthday("1743-04-13")
.build()
);
// More code here
}
Adding the Web Service Code
With the customer data created, you can add the web service code to the application.
-
Create the
CustomerService.java
class:View Code
package com.example.rest;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/customers")
public class CustomerService {
private final CopyOnWriteArrayList<Customer> cList = CustomerList.getInstance();
}The
CustomerService
class includes several imports. It starts with an@Path("/customers")
annotation, which specifies that the method calls appear under thehttp://localhost:8080/myapp/customers
path. The customer list is created and stored incList.
Next, you add two methods to the class. -
To return all customers in the list, add the
getAllCustomers
method:View Code
@GET
@Path("/all")
@Produces(MediaType.TEXT_PLAIN)
public String getAllCustomers() {
return "---Customer List---\n"
+ cList.stream()
.map(c -> c.toString())
.collect(Collectors.joining("\n"));
}This method has several annotations. The
@GET
annotation indicates that this method is called with the HTTP GET method. The@Path("/all")
annotation specifies the path used to call this method. In this case, callinghttp://localhost:8080/myapp/customers/all
returns the list of customers in plain text format. The@Produces(MediaType.TEXT_PLAIN)
annotation specifies that output data is returned in plain text format. Other formats could be specified, including JavaScript Object Notation (JSON) or XML. The lambda expression at the end of the class gets the list of customers, converts those object to strings, and then usesCollectors.joining("\n")
to return a single string. - To search for a customer in the list, add
the
getCustomer
method:View Code
@GET
@Path("{id}")
@Produces(MediaType.TEXT_PLAIN)
public String getCustomer(@PathParam("id") long id) {
Optional<Customer> match
= cList.stream()
.filter(c -> c.getId() == id)
.findFirst();
if (match.isPresent()) {
return "---Customer---\n" + match.get().toString();
} else {
return "Customer not found";
}
}The
@GET
annotation indicates that this method is called with the HTTP GET method. The@Path("{id}")
annotation specifies the path used to call this method. In this case, passing along
inhttp://localhost:8080/myapp/customers/101
turns the value 101 into anid
variable. The{id}
makes the specified value available as a parameter. The@Produces(MediaType.TEXT_PLAIN)
annotation specifies that output data is returned in plain text format. Other formats could be specified here, including JSON or XML. ThegetCustomer(@PathParam("id") long id)
annotation is where theid
parameter that was specified in thePath
annotation is turned into along
variable and passed to the method. The method uses a lambda expression to search for the specified ID. If it's found, the customer data is returned. If it isn't found, an error message is returned.
Note: If you want to view the complete listing, click this link: CustomerService.java.
Testing the Web Service
Starting the Grizzly/Jersey Web Service
In this section, you recompile the application and restart the Grizzly server.
-
If your Grizzly server is still running, press Enter to stop it.
-
Open a Terminal or Command Prompt window.
-
Change the project directory:
cd C:\examples\jersey-service.
-
Compile the project:
mvn clean compile.
-
Execute the project:
mvn exec:java.
Testing the Web Service in a Browser
In this section, you test the web service in a browser. In this example, Google Chrome is used.
-
In the address bar, enter
http://localhost:8080/myapp/customers/all.
View ImageDescription of this image A list of all customers is returned in plain text format.
-
In the address bar, enter
http://localhost:8080/myapp/customers/101.
View ImageDescription of this image The customer with an ID of 101 is returned in plain text format.
-
In the address bar, enter
http://localhost:8080/myapp/customers/109.
View ImageDescription of this image An error message is returned because no match was found.
Testing the Web Service with cURL
In this section, you test the web service with the cURL command-line tool. The tool is installed by default on most UNIX and Linux distributions. In Windows, the best way to use cURL is to install Cygwin (select cURL during the installation). You can also install cURL standalone.
- Get all customers:
curl -X GET -i http://localhost:8080/myapp/customers/all.
View Text Output
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Tue, 05 May 2015 21:40:18 GMT
Content-Length: 552
---Customer List---
ID: 100 First: George Last: Washington
EMail: gwash@example.com
City: Mt Vernon State: VA Birthday 1732-02-23
ID: 101 First: John Last: Adams
EMail: jadams@example.com
City: Braintree State: MA Birthday 1735-10-30
ID: 102 First: Thomas Last: Jefferson
EMail: tjeff@example.com
City: CharlottesVille State: VA Birthday 1743-04-13
ID: 103 First: James Last: Madison
EMail: jmad@example.com
City: Orange State: VA Birthday 1751-03-16
ID: 104 First: James Last: Monroe
EMail: jmo@example.com
City: New York State: NY Birthday 1758-04-28Each customer in the list is displayed.
- Get customer 101:
curl -X GET -i http://localhost:8080/myapp/customers/101.
View Text Ouput
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Tue, 05 May 2015 21:41:43 GMT
Content-Length: 118
---Customer---
ID: 101 First: John Last: Adams
EMail: jadams@example.com
City: Braintree State: MA Birthday 1735-10-30Customer 101 is found and returned.
- Get customer 109:
curl -X GET -i http://localhost:8080/myapp/customers/109.
View Text Output
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Tue, 05 May 2015 21:42:10 GMT
Content-Length: 18
Customer not foundAn error message is returned.
Creating an Uber JAR
An uber JAR is a JAR file that includes all
dependencies required to run the application. This
way, you can distribute your entire application in a
single file without messy class path variables or
extra software installations. To do this, you must
change the pom.xml
file.
Updating the pom.xml
File
If you use the default Maven settings to create a JAR file, only the class files generated from the source file are included in the JAR file. This process works fine when you're executing an application from Maven, because dependencies are downloaded into the local Maven cache. However, this process doesn't produce a standalone application file that can be run anywhere. For your application to run, you must include the Grizzly and Jersey libraries in your class path variable. When you include many libraries in your application, the class path can get rather messy.
Is there an easier way? Yes, there is, but you
need to change the pom.xml
file.
First, change the assembly plug-in. Here's the
XML code for the plug-in.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<finalName>jersey-service-${project.version}</finalName>
<archive>
<manifest>
<mainClass>com.example.rest.Main</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
Typically, you use the assembly plug-in to
create JAR or WAR files that package Java
applications into a file. Notice that the Main
class is set so that an executable JAR file is
created. Doing this allows the JAR file to
execute using the java -jar
command-line option. Also, notice that the FinalName
value was updated to match the rest of the
project.
So, now that you've created an executable JAR file, how do you add the application's required libraries? For that, you need the dependency plug-in:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals><goal>copy-dependencies</goal></goals>
</execution>
</executions>
</plugin>
When you add this plug-in with the copy-dependencies
goal, required libraries are copied into the JAR
file. All required class files to run the
application are included in the JAR file, and no
external class path or other settings are
required to execute the application.
Note: If you want to view the entire file, click this link: pom.xml.
Executing the Application
-
Change into the project directory.
-
Clean and compile the application:
mvn clean compile.
-
Package the application:
mvn package.
-
In the
target
directory, look for a file with the following name or a similar name:jersey-service-1.0-SNAPSHOT-jar-with-dependencies.jar.
-
Change to the
target
directory. -
Execute the JAR file:
java -jar jersey-service-1.0-SNAPSHOT-jar-with-dependencies.jar.
Your Grizzly/Jersey application executes and starts the service just like it did before from Maven. Because this JAR is self-contained, you can copy it to a different location on your machine or to another machine, and then execute it. The application is now completely self-contained.
Note: If you want to view the complete set of project files up to this point in the tutorial, download the source files and Maven project.
Considerations for Cloud Deployment
To properly package, deploy, and execute your application in Oracle Application Container Cloud Service, you need to understand a few key concepts.
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 a lightweight virtual machine. Your application runs in its own isolated execution space, which has its own memory, file system, and network access. So 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 the application. The implication is that the typical configuration information, such as host name and port number, is also dynamically generated. Therefore, any application that runs in this environment must 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 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, thereby "balancing" the load.
Given the service's setup, this has implications for your applications.
Application Considerations
For an application to run properly on Oracle Application Container Cloud Service, it must meet a few requirements:
- Applications must be stateless. Because the service can run multiple instances of the same application, applications can't share state. For example, items added to a shopping cart application on one instance aren't available to other instances of the application. Because application load is balanced between instances, there's no guarantee that the next request would be handled by the same instance as the last request. If you store the application state, you risk losing data. Therefore, store state information 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 with a running application is through its assigned network port.
- Applications must be configurable at runtime. Applications must be able to start and execute based on values set by the container. Values are passed to an application through environment variables.
- Applications must include all
dependencies. For example, if your
application requires a library for execution, you
must include the library with the application when
it's deployed. Here are the two ways you can do
that:
- Create an Uber Jar (Java SE only): When you create your application, include all dependent libraries in the JAR file with the application.
- Archived Libraries: Include
dependent libraries in the application archive
that you create. For Java, you can reference
separate JAR files by using the
CLASSPATH
command-line option in the application launch command.
Configuring Your Application to Run in the Cloud
To make your Java SE application run on Oracle
Application Container Cloud Service, you must let
the container assign the host name and port number
of your application. These values are set by the
container by using environment variables.
Specifically, your application needs to read the HOSTNAME
and PORT
environment variables, and
then start your application with those values.
In Java SE 8, the new Optional
class
makes it fairly easy to read these values, check for
null, and then return the values from the
environment or default values. The following is a
new version of the Main
class. This
version checks for the PORT
and HOSTNAME
environment variables at runtime. If you set the
values, the container uses them to launch the
server. If you don't set the values, the container
uses default values. This way, your application can
run standalone locally or in the cloud by using the
service.
Main.java
package com.example.rest;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import java.io.IOException;
import java.net.URI;
import java.util.Optional;
/**
* Main class
*/
public class Main{
// Base URI the Grizzly HTTP server will listen on
public static final String BASE_URI;
public static final String protocol;
public static final Optional<String> host;
public static final String path;
public static final Optional<String> port;
static{
protocol = "http://";
host = Optional.ofNullable(System.getenv("HOSTNAME"));
port = Optional.ofNullable(System.getenv("PORT"));
path = "myapp";
BASE_URI = protocol + host.orElse("localhost") + ":" + port.orElse("8080") + "/" + path + "/";
}
/**
* Starts Grizzly HTTP server exposing JAX-RS resources defined in this application.
* @return Grizzly HTTP server.
*/
public static HttpServer startServer() {
// create a resource config that scans for JAX-RS resources and providers
// in com.example.rest package
final ResourceConfig rc = new ResourceConfig().packages("com.example.rest");
// create and start a new instance of grizzly http server
// exposing the Jersey application at BASE_URI
return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
}
/**
* Main method.
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
final HttpServer server = startServer();
System.out.println(String.format("Jersey app started with WADL available at "
+ "%sapplication.wadl\nHit enter to stop it...", BASE_URI));
System.in.read();
server.stop();
}
}
Here are a few key points:
- Top of the class: Two
Optional<String>
variables are declared. Theport
andhost
fields store the result of the environment variable lookup. static
initialization block: TheSystem.getenv
method gets the environment variable. TheOptional.ofNullable
method is called. This method returns either the value stored in the environment variable or an emptyOptional
if no value is returned.BASE_URI
field: This field now uses theOptional
variables to create the URI. TheorElse
method sets a default value ifOptional
is empty.
With these improvements, you can set the host name or port by using environment variables. The additional code is clear and concise.
Note: If you want to view the complete set of project files up to this point in the tutorial, download the updated source files and Maven project.
Creating a Cloud-Ready Package with an Uber JAR
The final step in packaging your application is to
combine the application archive with the manifest.json
file, which provides application metadata. With this
final step, your application is ready for
deployment.
Adding Metadata to the manifest.json
File
The manifest.json
file provides
metadata about your Java application, including
the Java version and the command used to execute
the application. You can also include notes
about your application and release information.
{
"runtime": {
"majorVersion": "8"
},
"command": "bash -l start.sh",
"release": {
"build": "150520.1154",
"commit": "d8c2596364d9584050461",
"version": "15.1.0"
},
"notes": "notes related to release"
}
JSON Fields
majorVersion
is the version of the runtime. For example, Java SE 7 or 8.command
is the command to execute after you deploy the application.build
is the user-specified text value that identifies this build.commit
is the user-specified value for this commit.version
is the version text string that's maintained by the user.
A very minimal manifest.json
file
looks like this:
{
"runtime": {"majorVersion": "8" },
"command": "bash -l start.sh"
}
Here's the manifest.json
file
used in the sample application:
{
"runtime":{
"majorVersion": "8"
},
"command": "java -jar jersey-service-1.0-SNAPSHOT-jar-with-dependencies.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 uses Java SE 8. If your application needs special command-line switches to run, this is where you specify them.
Combining the Application Archive with the manifest.json
File
In this section, you create an archive that
contains the application archive and the manifest.json
files in the root directory of the archive file.
Here's an example of a compressed archive:
zip jersey-service-intro.zip
manifest.json
jersey-service-1.0-SNAPSHOT-jar-with-dependencies.jar
The resulting archive file has this structure:
/
|---> manifest.json
|---> jersey-service-1.0-SNAPSHOT-jar-with-dependencies.jar
Here's an example of a Tape Archive (TAR) file:
tar cvfz jersey-service-intro.tgz
manifest.json
jersey-service-1.0-SNAPSHOT-jar-with-dependencies.jar
Creating a Cloud-Ready Package Without an Uber JAR
As an alternative to an uber JAR, you can start
your application by specifying libraries on the Java
command line or by using a bash
shell
script. Because Oracle Application Container Cloud
Service uses a Linux container for the execution of
applications, Linux command line rules need to be
taken into consideration when packaging your
application.
Background
You can start your application by specifying
the required libraries on the Java command line.
The command line consists of the -classpath
or -cp
option which specifies the
location of the JAR files and the location of
the main class.
If you run the package
goal for a
Maven project without the Assembly plugin
section in the pom.xml
file, your
JAR file still gets built in the target
directory. However, all the required JAR files
are placed in a dependency
directory beneath the target
directory.
To run your application on Windows
from the target
directory use the
following command:
java -cp
jersey-service-1.0-SNAPSHOT.jar;dependency/*
com.example.rest.Main
Notice the generated JAR is specified along
with the dependency
directory and
the name of the main class. Using an asterisk
with the dependency
directory
loads all the JAR files in that directory into
the virtual machine as if they had been
specified individually. The third parameter
specifies the main class which starts the
application.
Note that on Windows the required JAR files were separated by a semicolon. The Java command line is operating system specific. Because Oracle Application Container Cloud Service uses a Linux container for the execution of applications, the launch command line is different on Linux.
To run the command line on Linux, the command would be modified like this:
java -cp
jersey-service-1.0-SNAPSHOT.jar:dependency/*
com.example.rest.Main
The semicolon is replaced with a colon to separate the names of JAR files.
Note: Therefore, this is the
command line which must go into your manifest.json
file when you deploy the application. The
following is a sample manifest.json
file using the code from our ongoing example.
{
"runtime": {
"majorVersion": "8"
},
"command": "java -cp jersey-service-1.0-SNAPSHOT.jar:dependency/* com.example.rest.Main",
"release": {
"build": "150520.1154",
"commit": "d8c2596364d9584050461",
"version": "15.1.0"
},
"notes": "notes related to release"
}
Packaging your Application
With the information provided, you can package your application for the Oracle Application Container Cloud Service.
- Put the application JAR file (
jersey-service-1.0-SNAPSHOT.jar
in this example) and themanifest.json
file in a directory. These two files will be in the root directory of the archive. - Copy the
dependency
directory underneath this directory. All required JAR files must be included in this directory. - Create the
.zip
or.tar.gz
archive from the same directory as your JAR and manifest file. Make sure you include subdirectories in your archive.
After these steps, you should have a packaged archive that is deployable on the service.
When you deploy the application to Oracle
Application Container Cloud Service, your
application JAR, manifest, and the dependency
directory are copied into the application's home
directory. The launch command executes from the
container's home directory which now contains
the required files. The application is launched
and deployed on the service.
Executing the Application with a Shell Script
If you execute your application with a shell script, set the following value for the launch command in the manifest.json file:
bash -l ./applicationScript.sh
The -l
option tells bash to start
the script as if it had been invoked as a login
shell.
Creating a Cloud-Ready Package: Putting It All Together
To properly package your application, here's what you need to do:
- Create an application that's configurable at runtime.
- Package your application as an uber JAR or as separate library JARs.
- Create an application archive that contains your
application and a
manifest.json
file in a single file. Store themanifest.json
file in theroot
directory of your archive.Note: The
manifest.json
file is optional because you can also configure the information in the user interface. - Deploy your application to Oracle Application Container Cloud Service.
Want to Learn More?
Credits
Curriculum Developer: Michael Williams