Sunday, April 9, 2017

Setting Up JEE 7 REST Web Application on Glassfish 4.1.1/4.1.2

Setting up a JEE web application in any of the many IDE's is typically pretty straightforward, but because of some issues with the glassfish libraries when using Jersey with Jackson, it turned out to be a little more challenging than expected.  Luckily, others have encountered the issue as well so it too difficult to figure out, however the answer also wasn't 100% straightforward so I decided to document the steps here.

This post will guide you through creating a JEE 7 web application that will contain a simple REST interface.  We'll be using IntelliJ IDEA Ultimate 2017.1, JEE 7, Java 8, Maven 3, and Glassfish 4.1.2 (same steps worked for 4.1.1).

Install Java 8.  There are plenty of guides out there on how to do this for the various platforms, so I will not cover it here.

Unzip the distribution and Glassfish is ready to go.

In IDEA, click File->New->Project and select Maven as the project type.  Ensure the Project SDK is set to Java 8, then check the Create from archetype checkbox and select org.apache.maven.archetypes:maven-archetype-webapp.



Click Next and fill in the GroupIdArtifactId, and Version fields, then click Next again twice.  Fill in a Project name and click Finish.

Since we're using Java 8, we need to tell IDEA and Maven to compile for Java 8.  Go to File->Settings->Build, Execution, Deployment->Java Compiler and for Project bytecode version select 1.8.

Also, go to File->Settings->Build, Execution, Deployment->Build Tools->Maven->Runner and ensure 1.8 is selected as the JRE version.

We'll need to setup the dependencies for Glassfish, Jersey, and Jackson in our pom.xml.  Add the dependencies, etc as shown below.

 <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>javax</groupId>  
    <artifactId>javaee-web-api</artifactId>  
    <version>7.0</version>  
    <scope>provided</scope>  
   </dependency>  
   <dependency>  
    <groupId>org.glassfish.jersey.containers</groupId>  
    <artifactId>jersey-container-servlet-core</artifactId>  
   </dependency>  
   <dependency>  
    <groupId>com.fasterxml.jackson.jaxrs</groupId>  
    <artifactId>jackson-jaxrs-json-provider</artifactId>  
    <version>2.6.3</version>  
    <scope>provided</scope>  
   </dependency>  
   <dependency>  
    <groupId>com.fasterxml.jackson.core</groupId>  
    <artifactId>jackson-annotations</artifactId>  
    <version>2.6.3</version>  
    <scope>provided</scope>  
   </dependency>  
   <dependency>  
    <groupId>org.glassfish.jersey.media</groupId>  
    <artifactId>jersey-media-json-jackson</artifactId>  
    <version>2.5.1</version>  
    <scope>provided</scope>  
   </dependency>  
  </dependencies>  
  <properties>  
   <jersey.version>2.24.1</jersey.version>  
  </properties>  
  <build>  
   <plugins>  
    <plugin>  
     <groupId>org.apache.maven.plugins</groupId>  
     <artifactId>maven-compiler-plugin</artifactId>  
     <configuration>  
      <source>1.8</source>  
      <target>1.8</target>  
     </configuration>  
    </plugin>  
   </plugins>  
  </build>  

IDEA will probably prompt you that changes were made to the pom.xml and you can have it automatically perform the import of the changes.  Go ahead and do this.

Inside of src/main/webapp/WEB-INF/web.xml add the following to setup Jersey and set out REST context to /webapi:

 <!DOCTYPE web-app PUBLIC  
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  
  "http://java.sun.com/dtd/web-app_2_3.dtd" >  
 <web-app>  
  <servlet>  
   <servlet-name>Books REST Example Application</servlet-name>  
   <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>  
   <init-param>  
    <param-name>jersey.config.server.provider.packages</param-name>  
    <param-value>com.sit.rest</param-value>  
   </init-param>  
   <load-on-startup>1</load-on-startup>  
  </servlet>  
  <servlet-mapping>  
   <servlet-name>Books REST Example Application</servlet-name>  
   <url-pattern>/webapi/*</url-pattern>  
  </servlet-mapping>  
 </web-app>  

Next we need to make sure all of the libraries required for Jersey, Jackson, etc are included with the war when it is built.  So go to File->Project Structure->Artifacts-><project name>:war exploded.  Under the Output Layout tab, expand WEB-INF/lib.  Then right click it and select Add Copy of->Library Files.  Select all of the libraries shown under Project Libraries.  Then click OK.  They should now appear under the lib folder in the Output Layout tab.

Click OK.

Next we need to create the REST resource, a model representing the resource, and a helper class that will contain our fake book data.

First create a java directory under src/main.  Right click it, then select Mark Directory as->Sources root so that IDEA treats the files there as source files.

Create BookResource.java under src/main/java/com/sit/rest.

 package com.sit.rest;  

 import com.sit.rest.model.Book;  

 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;  
 import javax.ws.rs.core.Response;  

 @Path("books")  
 public class BookResource {  
   @GET  
   @Path("{id}")  
   @Produces(MediaType.APPLICATION_JSON)  
   public Response getBook(@PathParam("id") int id) {  
     Book b = BookManager.getBookWithId(id);  
     if (b != null) {  
       return Response.status(Response.Status.OK).entity(b).build();  
     } else {  
       return Response.status(Response.Status.NOT_FOUND).build();  
     }  
   }  

   @GET  
   @Produces(MediaType.APPLICATION_JSON)  
   public Response getBooks() {  
     return Response.status(Response.Status.OK).entity(BookManager.getAllBooks()).build();  
   }  
 }  

Create Book.java under src/main/java/com/sit/rest/model.  It is important to note that you must have a parameter-less constructor.

 package com.sit.rest.model;
  
 public class Book {  
   private int id;  
   private String title;  
   private String author; 
 
   public Book() {  
   }  

   public int getId() {  
     return id;  
   }  

   public void setId(int id) {  
     this.id = id;  
   }  

   public String getTitle() {  
     return title;  
   }  

   public void setTitle(String title) {  
     this.title = title;  
   }  

   public String getAuthor() {  
     return author;  
   }  

   public void setAuthor(String author) {  
     this.author = author;  
   }  

   @Override  
   public String toString() {  
     return "Book{" +  
         "id='" + id + '\'' +  
         ", title='" + title + '\'' +  
         ", author='" + author + '\'' +  
         '}';  
   }  
 }  

Finally, create BookManager.java under src/main/java/com/sit/rest.

 package com.sit.rest;  

 import com.sit.rest.model.Book;  
 import java.util.ArrayList;  
 import java.util.List;  
 import java.util.Optional; 
 
 public class BookManager {  
   private static List<Book> books = new ArrayList<>();  

   static {  
     Book b = new Book();  
     b.setId(1);  
     b.setAuthor("Charles Dickens");  
     b.setTitle("Great Expectations");  
     books.add(b);  
     b = new Book();  
     b.setId(2);  
     b.setAuthor("Mark Twain");  
     b.setTitle("Tom Sawyer");  
     books.add(b);  
   }  

   static Book getBookWithId(int id) {  
     Optional<Book> book = books.stream().filter(b -> b.getId() == id).findFirst();  
     return book.orElse(null);  
   }  

   static List<Book> getAllBooks() {  
     return books;  
   }  
 }  

Next, we'll modify index.jsp located under src/main/webapp to add a couple links to our REST resource.

 <html>  
 <head>  
   <title>Books</title>  
 </head>  
 <body>  
 <p><a href="webapi/books">All Books</a>  
 <p><a href="webapi/books/1">Great Expectations</a>  
 <p><a href="webapi/books/2">Tom Sawyer</a>  
 </body>  
 </html>  

In order to run everything we need to setup IDEA to deploy to Glassfish by creating a configuration for it.  Click Run->Edit Configurations.  Click the "+" button and select Glassfish Server->Local.  Set the name to something like "Glassfish 4.1.1".  On the Server tab click Configure.  Click the "+" button to add a new Glassfish Server.  Choose the directory where you unzipped Glassfish at the beginning of this tutorial, then click OK.  For Server Domain, choose domain1.  On the Deployment tab click the "+" button and then select Artifact to add a deployment artifact.  Choose the "exploded" version.  Then click OK to complete the configuration.

You can now run the server and deploy the application by clicking the green Play button next to the configuration name in the upper right of the IDE.  Wait until the output shows that the artifact was deployed and it should also open a browser window showing the contents of the index.jsp we edited.

At this point you should get an internal server error that I will explain how to resolve next.

There are two issues discussed in the following two JIRA issues:
https://java.net/jira/browse/GLASSFISH-21440


The first issue (21440) involves modifying the MANIFEST.MF file in the org.eclipse.persistence.moxy.jar file in the Glassfish modules directory and adding org.xml.sax.helpers,javax.xml.parsers, javax.naming to the end of the Import-Package: block.  Modify it and save the jar.


The second issue deals with an error that occurs during the first request to a resource, but is not present on subsequent requests.  To resolve this issue, a different version of the JAXB Annotations module needs to be saved to the Glassfish modules directory in <glassfish_home>/glassfish/modules.  Download the jar at the following link and place it into the modules directory.
https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.5.1

In addition, we must update web.xml to add com.fasterxml.jackson.jaxrs.json to the param-value in the init-param for the servlet.

 <!DOCTYPE web-app PUBLIC  
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  
  "http://java.sun.com/dtd/web-app_2_3.dtd" >  
 <web-app>  
  <servlet>  
   <servlet-name>Books REST Example Application</servlet-name>  
   <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>  
   <init-param>  
    <param-name>jersey.config.server.provider.packages</param-name>  
    <param-value>com.sit.rest, com.fasterxml.jackson.jaxrs.json</param-value>  
   </init-param>  
   <load-on-startup>1</load-on-startup>  
  </servlet>  
  <servlet-mapping>  
   <servlet-name>Books REST Example Application</servlet-name>  
   <url-pattern>/webapi/*</url-pattern>  
  </servlet-mapping>  
 </web-app>  

Stop the Glassfish server by going to the Run tab and clicking the red Stop button.  Then, clear out the Glassfish osgi-cache by deleting everything in the directory located at:

<glassfish_home>/glassfish/domains/domain1/osgi-cache/*  

On the Run tab within IDEA, click the Play button to the left of the Server tab to start the Glassfish server.

Now the links in index.jsp should work as expected and return a JSON formatted response.

/webapi/books



The code for this post can be found at https://github.com/kglee79/jee7-glassfish-rest-example