Java SE 8: Creating a Basic REST Web Service by Using Grizzly, Jersey, and Maven for Oracle Application Container Cloud Service

 

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?

 

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.

  1. Navigate to the directory where you want to create a Grizzly/Jersey project (for example, c:\examples).

  2. Create an empty project:

    mvn 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. The archetypeGroupId specifies which group this archetype is defined in. The groupId uniquely identifies the project, should be based on your domain name, and typically matches your package name. The artifactId 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:

    Project's directory structure
    Description 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.

  1. Open a Terminal or Command Prompt window.

  2. Change the project directory: cd C:\examples\jersey-service.

  3. Compile the project: mvn clean compile.

  4. 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:


    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...
  5. Open a Chrome web browser to test the web service.

  6. 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.

    Chrome browser window with http://localhost:8080/myapp/myresource URL
    Description 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.

  1. 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. A long is used to store the ID field. The other fields are strings.


    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;
  2. 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.


    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.

  1. Create the CustomerService.java class:


    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 the http://localhost:8080/myapp/customers path. The customer list is created and stored in cList. Next, you add two methods to the class.

  2. To return all customers in the list, add the getAllCustomers method:


    @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, calling http://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 uses Collectors.joining("\n") to return a single string.

  3. To search for a customer in the list, add the getCustomer method:

    @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 @GETannotation 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 a long in http://localhost:8080/myapp/customers/101 turns the value 101 into an id 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. The getCustomer(@PathParam("id") long id) annotation is where the id parameter that was specified in the Path annotation is turned into a long 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.

  1. If your Grizzly server is still running, press Enter to stop it.

  2. Open a Terminal or Command Prompt window.

  3. Change the project directory: cd C:\examples\jersey-service.

  4. Compile the project: mvn clean compile.

  5. 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.

  1. In the address bar, enter http://localhost:8080/myapp/customers/all.

    Chrome browser window with http://localhost:8080/myapp/customers/all URL
    Description of this image

    A list of all customers is returned in plain text format.

  2. In the address bar, enter http://localhost:8080/myapp/customers/101.

    Chrome browser window with http://localhost:8080/myapp/customers/101 URL
    Description of this image

    The customer with an ID of 101 is returned in plain text format.

  3. In the address bar, enter http://localhost:8080/myapp/customers/109.

    Chrome browser window with http://localhost:8080/myapp/customers/109 URL
    Description 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.

  1. Get all customers: curl -X GET -i http://localhost:8080/myapp/customers/all.

    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-28

    Each customer in the list is displayed.

  2. Get customer 101: curl -X GET -i http://localhost:8080/myapp/customers/101.

    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-30

    Customer 101 is found and returned.

  3. Get customer 109: curl -X GET -i http://localhost:8080/myapp/customers/109.

    HTTP/1.1 200 OK
    Content-Type: text/plain
    Date: Tue, 05 May 2015 21:42:10 GMT
    Content-Length: 18

    Customer not found

    An 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

  1. Change into the project directory.

  2. Clean and compile the application: mvn clean compile.

  3. Package the application: mvn package.

  4. 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.

  5. Change to the target directory.

  6. 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. The port and host fields store the result of the environment variable lookup.
  • static initialization block: The System.getenv method gets the environment variable. The Optional.ofNullable method is called. This method returns either the value stored in the environment variable or an empty Optional if no value is returned.
  • BASE_URI field: This field now uses the Optional variables to create the URI. The orElse method sets a default value if Optional 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.

  1. Put the application JAR file (jersey-service-1.0-SNAPSHOT.jar in this example) and the manifest.json file in a directory. These two files will be in the root directory of the archive.
  2. Copy the dependency directory underneath this directory. All required JAR files must be included in this directory.
  3. 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:

  1. Create an application that's configurable at runtime.
  2. Package your application as an uber JAR or as separate library JARs.
  3. Create an application archive that contains your application and a manifest.json file in a single file. Store the manifest.json file in the root directory of your archive.

    Note: The manifest.json file is optional because you can also configure the information in the user interface.

  4. Deploy your application to Oracle Application Container Cloud Service.
 

Want to Learn More?

 

Credits

Curriculum Developer: Michael Williams