Login

REST-MVC using Java

Article Overview

This article is part of a series. We use the "book club" project to explore various programming languages and frameworks. Details of the book club's business and data requirements are detailed in a prior article, "Leveraging Ruby on Rails and ClojureScript.".

This article details a new server tier implementation. We employ a stack of Java libraries and tools. Source code and build files are located on GitHub, at the book-site-jpa repository.

Server Tier Software Stack

Spring

Spring was originally designed as a lightweight alternative to J2EE. Thus, Spring provides the "big picture" benefits of J2EE. In particular, you can change your application components via Spring's configuration (I.E. instead of changing your application's source code).

Let's look at the historical perspective before going to some of Spring's details.

J2EE was designed for large applications and large organizations. Hence the term "enterprise". When J2EE was first introduced, it defined several development roles within an organization (see Chapter 3 - Applying Enterprise JavaBeans). An "application assembler", "deployer" and "system administrator" combined to serve as a "application server administrator". A role, or group, who's responsibility is similar to a database administrator (DBA). Instead of administering a database, these developers focus on solving an application's business logic by configuring the J2EE components and settings.

Managing Spring's configuration is much simple than the original J2EE specification. The latest J2EE specification has adopted much of it's simplifications from Spring.

Annotation versus XML configuration settings.

Actually, I shouldn't use the word "versus". Almost all of Spring's settings can be now performed through annotations. In addition, you can use both XML and annotated settings together. If your shop is large enough to have a dedicated "application server administrator", you may prefer to keep settings in XML files. XML files are external to the Java source code files. Thus, an "administrator" can tweak the application without touch the source (when settings stored in XML). Vice versa. If your shop combines development with operations, then you may prefer storing settings in an annotated form.

Note! The same is true for other components of our stack (Hibernate, Jersey, JPA, etc). We can define setting in both annotations and XML.

Data Persistence

We'll use Hibernate to provide a transaction manager and map our database schema to Java objects. Java Persistence API (JPA) adds a vendor agnostic API on top of Hibernate.

Spring allows us to annotate all the services together. Spring allows us to inject transacation manager services and JPA repositories (classes which provide basic CRUD functionality).

H2 Database Engine

We use the H2 Database engine in "server mode" as our system database. H2 is very lightweight and easy to set up.

Business Logic

Spring MVC provides a framework for our business logic. We locate most of business logic in Spring Controllers.

Views
Spring's MVC provides a framework for many different types of views. For the book club, we are using web page views (SPRING MVC + JSP/HTML/CSS etc.). Most of the web page artifacts are generated by the server dynamically.

Note! We also implement web pages which are generated dynamically by the web browser (client). We implement a set of Spring Controllers which communicate via JSON with a RESTful interface. See "Jersey JAX-RS/JSON" below.

Web Server - Servlet Engine

For the book club, we are using Jetty as our Web server and servlet engine. Specifically version 9.0.4 of Jetty.

CSS Framework

We also use the Pure CSS framework to layout the web page views.

Jersey JAX-RS/JSON

We use Jersey JAX-RS to ready JSON data sent by our Clojurescript client. We also use Jersey to provide a JSON representation of Java objects when preparing a response (to the client).

Implementation Details

In this section we'll look at a few places where we needed to either add too, or override the server tier defaults.

Distributing Controllers

Spring MVC controllers are servlets. You can configure your controllers to span 1 to many servlets. In our case, we load 2 servlet instances. One servlet to host the controllers which dispatch traditional JSP/HTML views. One servlet to host the controllers which communicate via a REST interface with JSON.

One way to scale the application is to add more servlet instances. You can profile you application to see if you get a performance gain. For our application, we could distribute the controller logic so that each entity had it's own servlet (to run on). For example, one servlet to host the Author controller (traditional views), another servlet to host the Books controller (traditional views), etc.

To summarize, distributing the controller logic is an option.

REST Controllers

For this application we are using Javascript Object Notation (JSON) to communicate between client and server. Spring MVC provides several configuration options when you want your controllers to communicate via JSON.

One option is to define a server path which includes a ".json" suffix in the request patterns. That works, however we wanted to maintain a standard Representational state transfer (REST) interface. A standard REST interface defines simple paths for the client to request (E.G. Modify an Author requested as a HTTP PUT using the following pattern /modify/authors/{id}/{firstName}/{lastName}).

There is a simple way to configure Spring MVC so that it provides a standard REST interface. We configured a servlet instances which is dedicated to REST controllers. You can peruse the web.xml for further details.

Note! For an example REST controller method, see the section below "When we don't want "Eager Loading" (last figure).

Customizing Jersey's JSON Generation

We use Jersey (a library) to generate a JSON representation of our data. We share information with our Clojurescript client via JSON.

Our server tier defines a few database fields that the client doesn't need. Therefore we need to instruct Jersey to skip those fields when generate JSON data.

Jersey allows us to controll it's configuration via annotations. We can annotate our entity classes (java classes used by Hibernate and JPA to map to the data).

Let's look at an example.

When the Clojurescript client asks for an Book, we don't need the related data (E.G. the first and last name of the author, the reviews of the book). A simple annotation placed on the Book entity class, centralizes the logic. Our REST controller does not need to specify which fields to include in the JSON representation of a Book. Jersey reads the annotation setting from entity class and automatically fulfills our desired setting.

Back to our example, a request for a Book. Here is the annotation added to our Book entity (org.pa.entity.Book).

@JsonIgnoreProperties({"authorId","reviewsCollection","bookCategoriesCollection", "createdAt","updatedAt" })

We've instructed Jersey to exclude the Book's author (authorId), any reviews of the book (reviewsCollection), categorizations of the book (bookCategoriesCollection) and finally database time stamps (createdAt and updatedAt).

JSON Messaging
In the previous section we discussed creating a JSON representation of entity data (E.G. Book. Authors, etc). Here, we discuss packaging up the entity data in to a "message". We discuss how we route the message from the server to the client.

We route all application messages with a HTTP status code of 200. This strategy allows us to easily differentiate application messages from communication exceptions (or events like a redirect).

When the client gets a HTTP 200 status message, we know the communication succeeded. We have successfully communication an application message.

When the clients gets an HTTP message with any status other than 200, we know there is a communication exception.

Next, the client needs to determine whether the application message represents a SUCCESS or FAILURE. At the server tier, we've added some additional message fields to help the client. If the message contains an "entity" property, the application logic succeeded. If the message contains a "exception" property the application logic failed.

I've also included some additional fields anticipating enhancements to the application. For example, our client might be running on a mobile device. A mobile device does not always a connection to the internet. If you are riding in your car the client could loose connectivity in the middle of a transaction. For example, in the middle of updating a book.

So each JSON application success message includes the following:

  • Status (e.g. INFO, WARNING, SUCCESS ,etc -- included for future enhancements)
  • Action (e.g. message is response to request to add a new Author)
  • Data (e.g. an image of the new Author, including the server generated key)
  • Entity (e.g. Author, Book, etc)

So fields like "action" aren't used right now. However, later we can use the "action" field to handle an interrupted transaction.

Application Messages
To implement our application message strategy, we employ 2 helper classes to package up our data.

  • org.pa.rest.message.SuccessMessage
  • org.pa.exception.RestException

The following snippet demonstrates usage. This example is from the Spring controller which services REST requests for Author entities (org.pa.rest.controller.AuthorRest). The controller needs to catch an attempt to add a duplicate author.

@RequestMapping(value = ADD_AUTHOR_URL, method = RequestMethod.POST, produces = "application/json")
 @ResponseStatus(HttpStatus.OK)
 public @ResponseBody
Object addAuthor(@PathVariable String firstName, @PathVariable String lastName) {
  Author author = null;
  try {
      author = new Author();
      author.setFirstName(firstName);
      author.setLastName(lastName);
      AuthorValidator authorValidator = new AuthorValidator();
      Map validationMap = new HashMap();
      validationMap.put("lastName", author.getLastName());
      validationMap.put("firstName", author.getFirstName());
      BindingResult result = new MapBindingResult(validationMap, "author");
      authorValidator.validate(author, result);
      if (result.hasErrors()) {
         RestException re = new RestException(MessageDetailDefinitions.ADD_AUTHOR_EXCEPTION);
         re.putTarget("author", author);
         return re.exceptionMap();
      }
      authorsRepository.addNew(author);
      SuccessMessage message =
        new SuccessMessage(MessageDefinitions.ADD_OPERATION, MessageDefinitions.AUTHOR_ENTITY, author);
      return message;
   } catch (Exception pe) {
      RestException re;
      if (pe.getCause() instanceof ConstraintViolationException) {
         re = new RestException(MessageDetailDefinitions.DUPLICATE_AUTHOR_EXCEPTION);
      } else {
         re = new RestException(MessageDetailDefinitions.SAVE_AUTHOR_EXCEPTION);
    }
    re.putTarget("author", author);
    return re.exceptionMap();
  }
}

If the above "Add Author " method succeeds, it returns a SuccessMessage object. The annotation at the top of the method (produces = "application/json") instructs Spring to call Jersey and return a JSON representation of the SuccessMessage object.

Spring Validation API

Spring provides a standard API to implement input validation. If you peruse the about Author controller code, you can see we first validate the input.

I use the Validation API for both types of controllers (View and REST). The implementations are very simple. I've located the implementations in the java package "org.pa.validation".

The only trick is entity dependencies. For example, our Book entities depends on the existence of an Author. Thus, the Book validator must also validate the corresponding Author. Here is our Book validator source:

public class BookValidator implements Validator { 
  private final Validator authorValidator;
  public BookValidator() {
        authorValidator = new AuthorValidator();
  }
   
  public BookValidator(Validator authorValidator) {
     if (authorValidator == null) {
       throw new IllegalArgumentException(
         "The supplied [Validator] is required and must not be null.");
   }
   if (!authorValidator.supports(Author.class)) {
      throw new IllegalArgumentException(
        "The supplied [Validator] must support the validation of [Author] instances.");
   }
   this.authorValidator = authorValidator;
  }   
    
  @Override
  public boolean supports(Class<?> clazz) {
   return Book.class.isAssignableFrom(clazz);
  }

  @Override
  public void validate(Object target, Errors errors) {
    ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title", "required", "Field is required.");
    Book book = (Book) target;
    try {
       errors.pushNestedPath("authorId");
       ValidationUtils.invokeValidator(this.authorValidator, book.getAuthorId(), errors);
    } finally {
       errors.popNestedPath();
   }
 }
}

View Controllers

As mentioned above, we also implement a tradition JSP/HTML client (I.E. web pages are generated dynamically by the server tier).

Here is an example of Book controller, list all books method (listing is located in "org.pa.controller.BookController").

private final static String LIST_URL = "book/list"; 
@RequestMapping(value = {LIST_URL, "index.jsp"}, method = RequestMethod.GET)
public ModelAndView list() throws Exception {
  List bookList;
  bookList = booksRepository.findAll();
  ModelAndView mav = new ModelAndView(LIST_URL);
  mav.addObject("list", bookList);
  return mav;
}

Our JSP source is located in "WEB-INF/views/book/list.jsp".

<%@page import="org.pa.entity.Book">
<%@page import="java.util.List"%>
<jsp:include page="/jspf/pageHeader.jspf" />
<% List list = (List) request.getAttribute("list");
    String appPath = request.getServletContext().getContextPath();
%>
<table class="pure-table" style="letter-spacing:normal;">
  <thead><tr>
    <th>Title</th>
    <th>Author</th>
     <th></th>
     <th></th>
  </tr></thead>
  <tbody>
  <div><a href="<%= appPath%>/book/add.htm">Add New Book</a></div>
  <% for (Book book : list) {%>
   <tr>
    <td><%= book.getTitle() %><%= book.getAuthorId().getLastName() %></td>
    <td><%= book.getAuthorId().getFirstName() %></td>
    <td><a href="<%= appPath%>/book/edit.htm?id=<%= book.getId()%>">Edit<a href="<%= appPath%>/book/delete.htm?id=<%= book.getId()%>">Delete</a></td>
   </tr>
 <%  }%>
</tbody>
</table>
<%@include  file="../pageFooter" %>

Mapping Views with Spring MVC

We need to tell Spring MVC where to find our JSP pages. In this case, I used a combination of annotated settings and XML.

The standard Java Web applcation setting file is WEB-INF/web.xml. In the following section of web.xml, I define an instance of the Spring MVC serlet, and pass the name of a configuration file.

<servlet>
   <servlet-name>dispatcher</servlet-name>
    <servlet-class>
       org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
       <param-value>/WEB-INF/dispatcher-context.xml

And then in the "dispatcher-context.xml" file, the following section tells Spring MVC we are using JSP views (viewClass), and the location of the JSP source.

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
 <property name="viewClass"  value="org.springframework.web.servlet.view.JstlView" />
 <property name="prefix" value="/WEB-INF/views/" />
 <property name="suffix" value=".jsp" />
</bean>

ORM Eager Loading

"Eager Loading" is typically an ORM setting. You are instructing the ORM (Hibernate) to select all related records. For example, each Book has a related Author. Therefore, when Hibernate selects a Book, by default, it will also select the Book's author.

When we configured Hibernate for our application, we defined the relationship between Books and Authors. The Book entity implementation is located in "org.pa.enity.Book". The following annotated property defines the relationship between books and authors:

 @JoinColumn(name = "AUTHOR_ID", referencedColumnName = "ID")
 @ManyToOne(optional = false)
 private Author authorId;

"Eager Loading" helps a programmer quickly prototype an application. The programmer defines the database table relationships in the ORM configuration. For example, in the following snippet we select a Book by it's id value. Then using the Book entity object, we select the book's author.

Book book = booksRepository.findById(id);
Author markTwain = book.getAuthorId();

Thus, the case for the "Eager Loading" default setting.

When we don't want "Eager Loading"
Our application does have a use case where we don't want all the related records. When we prepare JSON data to be sent from the server to the Clojurescript client. For example, in the listing "org.pa.rest.controller.BookRest". BookRest is a Spring Controller. The "exportToJson()" method produces an JSON array (of all books). Each book should only contain the book's id, title and author_id.

public final static String EXPORT_ALL_URL = "/export/book/all";
@RequestMapping(value = EXPORT_ALL_URL, method = RequestMethod.GET, produces = "application/json")
@ResponseStatus(HttpStatus.OK)
public @ResponseBody
Map exportToJson(Model model) throws Exception {
  Map dto = new HashMap<>();
  List list = booksRepository.exportAll();
  dto.put("books", list);
  return dto;
}

The method "exportAll()" is located in the Book repository implementation, "org.pa.repository.BooksRepositoryImpl". Here is the method source:

@Transactional
public List exportAll() {
 TypedQuery q = (TypedQuery) entityManager.
  createQuery("select new org.pa.dto.BookExport(a.id, a.title, a.authorId.id) from Book AS a",BookExport.class);
  List list = q.getResultList();
  return list;
}

A simple override of the defailt Hibernate results. We've cast the results to a simple Plain Old Java Object (POJO), called "BookExport". The source code for "BookExport" is located at "org.pa.dto.BookExport". Heres is the complete source for BookExport:

package org.pa.dto;
import java.io.Serializable;
public class BookExport implements Serializable {
    
    public String title;
    public Integer id;
    public Integer author_id;

    public BookExport() {}
 
    public BookExport( Integer id,String title, Integer author_id) {
        this.title = title;
        this.id = id;
        this.author_id = author_id;
    }    
}

Implementing an Entity Class

Hibernate maps our database schema to java objects. JPA adds a vendor agnostic interface on top of Hibernate. There many IDE plugins and various tools which will generate the boilerplate code (and Hibernate/JPA annotations. settings. etc). So. let's just look at a snipplet of the Author entity class (org.pa.entity.Author.

@Entity
@Table(name="Authors")
public class Author implements Serializable {
  private static final long serialVersionUID = 1L;
  
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  private Integer id;

  @Basic(optional = false)
  @Column(name = "FIRST_NAME")
  private String firstName;

We create a java class that represents the Authors database table. The first name field is required (I.E. can not be null).

Implementing a Repository
Specifically we are implementing a Spring JPA repository. To summarize, a repository is a java class which is responsible for create, read, update and delete (CRUD) operations. CRUD operations that run against the ORM and ultimately, the database.

Let's look at a snippet of the AuthorRepository implementation (org.pa.repository.AuthorsRepository).

@Service
public class AuthorsRepositoryImpl implements AuthorsRepository {

  @PersistenceContext
  private EntityManager entityManager;

  @Transactional
  public Author update(Serializable id, String first_name, String last_name) throws Exception {
      Author author = findById(id);
      author.setFirstName(first_name);
      author.setLastName(last_name);
      entityManager.merge(author);
      return author;
  }

  @Override
  @Transactional(readOnly = true)
  public List findAll() {
      Query q = entityManager.createQuery("select a from Author a");
      List list = q.getResultList();
      return list;
  }

Note! the @Transactional annotation. That instructs Spring to inject not only a transaction manager, but transaction operations like begin, commit, rollback etc.

Maven Build Tool
We use Apache Maven to automate several tasks.

Maven manage libraries (I.E. java jar files) for us. That means Maven creates a set if directories on my development machine. I don't have to specify the exact directory location when I need a library when compiling or deploying.

Maven also includes a test runner (and reporter).

The Maven settings file is located in the root of the project, pom.xml.

Maven also provides a standard directory structure for our project's source. That standard directory structure makes our project IDE agnostic. We can use and IDE or editor to maintain and deploy the application.

Note! I customized the application deployment (i.E. the task of copying the "war" file in to Jetty's deployment directory). I added the "ant-run" plugin. The ant-run plugin allows you to include Apache Ant commands in the build. deployment or tests. Apache Ant is like a OS agnostic command shell.

Specifically, I did not want to deploy a "war" file in Jetty. While working on the Clojurescript client, I wanted an "exploded" version of the web app deployed instead. "Exploded" just means, the full directory structure is run by Jetty instead of single archive file (war file). Running the application with the full directory structure, allowed me to easily change the JavaScript file produced by Clojurescript. While developing the Clojurescript client, the exploded deployment was important.

Using Ant to copy the full application directory structire was fairly easy. The following section does all the work.

<execution>
 <phase>package</phase>
   <configuration>
    <target>
     <property name="jetty.home" value="${user.home}/development/jetty/jetty-9.0.4"/>
     <delete dir="${jetty.home}/webapps/book-site-jpa"/>
     <copy todir="${jetty.home}/webapps/book-site-jpa">
      <fileset dir="target/book-site-jpa-${version}"/>
     </copy>
    </target>
   </configuration>
   <goals>
    <goal>run</goal>
   </goals>
</execution>

I always like to note that build tools like Maven and Ant are optional. Maven and Ant automate tasks that can all be performed manually.

Configuring Jetty
I used Jetty version 9.0.4 for this portion of the project. I only needed a to tweak a few settings.

Custom Favicon
Jetty's default servlet needs a setting to support your application's "favicon" (I.E. the logo type graphic displayed in a web browser's location bar). The "default handler" is located in "etc/jetty.xml". You just set the "serveIcon" property to false. The following shows the shows the original setting commented out, replaced by the desired setting.

<!--<New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>-->
<New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler">
  <Set name="serveIcon">false</Set>
</New>

Note! You also need to check the settings file "start.ini" (located in the root directory of your Jetty installation). Above we customized the default handler in the jetty.xml file. In "start.ini" remove the comment in front of "/etc/jetty.xml".

Enable JSP
Again, in the "start.ini" file (located in the root directory of your Jetty installation), remove the comment located in front of "OPTIONS=jsp".

Annotion Support
In "start.ini" include the follwing statements:

OPTIONS=annotations
etc/jetty-annotations.xml

These settings gives us the option to annotate servlet settings (versus placing the settings in the web.xml file).

etc/webdefault.xml

I also set the following in webdefault.xml
- Turned off the display of file directories.
- Turned on custom welcome files (e.g. index.htm)

Running Jetty
Running Jetty is simple. From the command line, navigate to the root directory of your Jetty installation. Issue the following command:

$jetty> java -jar start.jar

Summary
The stack of components for this server tier implementation may seem like a lot to manage. However, once you are familiar with the various components, assembling them together is really not that much work. I probably spent more time writing the article than I did the code :).

First we have to remember Java tooling takes care of a lot of the boiler plate code. The verbose nature of the boiler plate code actually is an advantage when you need to scale up (E.G. add clustering).

Article references.
As mentioned at the top, this article is part of a series focused on the "book club" project.

For this release of the book club project:
Changes to the Clojurescript client discussed in ClojureScript - Single Page Application - A simple approach
Testing the Server Tier discussed in Testing the Java REST-MVC Server Tier
Testing the Clojurescript Client is discussed in Testing Clojurescript - A simple approach
GitHub Repository for Server tier: book-site-jpa
GitHub Repository for Clojurescript Client: book-site-clojurescript

About the Author:
Lorin M Klugman - I'm an experienced developer. My main interest is in new technology. Please use our contact box here if you are interested in hiring me. Please no recruiters :)