Sunday, May 3, 2015

Code Snippets

Open a File

try (FileInputStream fis = new FileInputStream(file)) {

 try (BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {

  String line = reader.readLine();
  System.err.println(line);

  while (line != null) {
   line = reader.readLine();
   System.err.println(line);
  }
 }
}

Thursday, April 9, 2015

GET and POST with JAX-RS

Environment

  1. Apache Tomcat 7 (dockerized version)
  2. MongoDB 2.6.7 (dockerized version)
  3. JDK 7
  4. Jersey Server 1.9
  5. Spring 4.1.6
  6. Clients: Java, Bash, Python, Javascript



Introduction and Project Description



  • A JAX-RS web application implemented in Jersey
  • Build Automation is handled using Maven
  • Deployment Environment is Apache Tomcat 7 (dockerized version)
  • Consumer can POST or GET a book entity.
    • Data is persisted in MongoDB (dockerized version)
    • The Spring Data Framework (1.7.0) is used to wrap the MongoDB Java Driver (2.11.0)
  • The Spring Framework (4.1.6 BOM) is used for component management
  • Consumers are provided in Java, Python and Javascript (jQuery 2.x)
What's new and different?  Perhaps not much, depending on your background.  I spent most of my early years coding JEE systems using Servlets and Web Services and relational databases with ORM providers to handle the mapping.  Moving to JAX-RS seems cleaner, and MongoDB (in spite of the cumbersome syntax in Java) is far easier for simple persistence.  No ORM mechanism required.

The application component model:

Fig 1: Component Model

The consumer can POST or GET a book entity to the application.

A JSON representation of the book entity looks like this: {"author":"Patrick OBrian","id":123,"title":"The Wine Dark Sea"}

The project source code is available at Github (reference below).


GET


The JAX-RS compliant-interface looks like:
@Path("/endpoint")
public class RestEndpoint {

 public static LogManager logger = new LogManager(RestEndpoint.class);

 @GET
 @Path("/get")
 @Produces(MediaType.APPLICATION_JSON)
 public Object get1(@QueryParam("id") Long id) {
   return getController().get(id);
 }

 @GET
 @Path("{id}")
 @Produces(MediaType.APPLICATION_JSON)
 public Object get2(@PathParam("id") Long id) {
  return get1(id);
 }

 // define POST methods ...
}

Using the first GET method, a consumer can query the system like this:
$ curl http://host:8080/blogger/demo/endpoint/123
{"author":"Patrick OBrian","id":123,"title":"The Wine Dark Sea"}

and use the second method like this:
$ curl http://host:8080/blogger/demo/endpoint/get?id=123
{"author":"Patrick OBrian","id":123,"title":"The Wine Dark Sea"}

Using the requests module in Python, the GET methods look like this:
import requests 

def g1():
 r = requests.get('http://host:8080/blogger/demo/endpoint/123')
 test(r.status_code)

def g2():
 payload = { 'id': '123' }
 headers = {'content-type': 'application/json'}
 r = requests.get('http://host:8080/blogger/demo/endpoint/get', params=payload)
 test(r.status_code)

def test(status_code):
 print 'status_code:', status_code
 assert (200 == status_code or 201 == status_code)

g1()
g2()

Using jQuery, the GET looks like this:
<html>
<head>
<script src="js/jquery-2.1.3.js"></script>
<script>

 var BASE = "http://host:8080/blogger/demo/endpoint/";

 $(document).ready(function() {
  $("#btnSubmitGET").click(function() {
   get($("#txtGET").val());
  });
 });

 /* get data by id */
 function get(id) {
  var url = BASE + id;
  $.getJSON(url, function(data) {
   /* create a list from the results */
   var items = [];
   $.each(data, function(key, val) {
    items.push("<li id='" + key + "'>" + val + "</li>");
   });

   $("<ul/>", {
    "class" : "my-new-list",
    html : items.join("")
   }).appendTo("body");
  });
 }

</script>
</head>

<body>
 <input type="text" name="id" value="9956" id="txtGET" class="id" />
 <input id="btnSubmitGET" type="submit" value="GET" />
</body>
</html>

and the result like this:
Fig 2: Results
Note: The example above is only testing the first GET method, but extending this into the second method (get?id=123) would be trivial.


POST


I've defined three POST methods that look like this:
@POST
@Path("/post")
@Consumes(MediaType.APPLICATION_JSON)
public Response post1(Book book) {
 logger.info("Invoked Post Method 1:\n\t%s", BookAdapter.toString(book));
 return getController().post(book);
}

@POST
@Path("/form")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response post2(@FormParam("id") long id, @FormParam("author") String author, @FormParam("title") String title) {
 logger.info("Invoked Post Method 2:\n\tid = %s, author = %s, title = %s", id, author, title);
 return post1(BookAdapter.transform(id, title, author));
}

@POST
@Path("/{param}")
public Response post3(@PathParam("param") String msg) {
 logger.info("Invoked Post Method 3:\n\t%s", msg);
 try {

  Book book = GsonUtils.toObject(msg, Book.class);
  if (null != book) return post1(book);

 } catch (JsonSyntaxException e) {
  logger.error(e, "Invalid JSON (%s)", msg);
 }

 return ResponseAdapter.postFail("book", msg);
}

In the first case, I can GET and POST a Book entity from a Java consumer, like this:
package com.trimc.blogger.jaxrs;

import static org.junit.Assert.assertEquals;

import javax.ws.rs.core.MediaType;

import org.swtk.common.framework.LogManager;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.trimc.blogger.jaxrs.dto.Book;
import com.trimc.blogger.jaxrs.dto.BookAdapter;

public class WebResourceTester {

 public static LogManager logger = new LogManager(WebResourceTester.class);

 public static void main(String... args) throws Throwable {

  Client client = Client.create();
  Book book = BookAdapter.transform(12345l, "Nutmeg of Consolation", "Patrick O'Brian");

  WebResource webResourcePost = client.resource("http://host:8080/blogger/demo/endpoint/post");
  ClientResponse responsePost = webResourcePost.type(MediaType.APPLICATION_JSON).post(ClientResponse.class, book);

  logger.info("%s", responsePost.getEntity(String.class));

  WebResource webResourceGet = client.resource("http://host:8080/blogger/demo/endpoint/" + book.getId());
  ClientResponse responseGet = webResourceGet.type(MediaType.APPLICATION_JSON).get(ClientResponse.class);

  Book _book = responseGet.getEntity(Book.class);
  assertEquals(book.hashCode(), _book.hashCode());
 }
}

In the form POST method, I can use standard HTML:
<html>
<head>
<body>
 <h3>Demonstrate HTML Form POST</h3>
 <form action="http://192.168.1.7:8080/blogger/demo/endpoint/form"
  method="post">
  <p>
   ID : <input type="text" name="id" value="123" />
  </p>
  <p>
   Author : <input type="text" name="author" value="Patrick OBrian" />
  </p>
  <p>
   Title : <input type="text" name="title" value="The Wine Dark Sea" />
  </p>
  <input type="submit" value="POST" />
 </form>
</body>
</html>


Fig 3: Form POST

and the result demonstrated via RoboMongo:

Fig 4: JSON Results

The third method in jQuery looks like this:
<html>
<head>
<script src="js/jquery-2.1.3.js"></script>
<script>
 var BASE = "http://host:8080/blogger/demo/endpoint/";

 $(document).ready(function() {
  initTextarea();
  $("#btnSubmitPOST").click(function() {
   post($("#txtPOST").val());
  });
 });

 /* initialize the textarea with a payload */
 function initTextarea() {
  var payload = {
   "author" : "Patrick O'Brian",
   "id" : 1234,
   "title" : "The Wine Dark Sea"
  };

  $("#txtPOST").val(JSON.stringify(payload));
 }

 /* post json data */
 function post(id) {
  try {

   var url = BASE + id;
   $.post(url, function(data, status) {
    alert("Data: " + data + "\nStatus: " + status);
   });

  } catch (e) {
   alert(e);
  }
 }
</script>
</head>

<body>
 <h3>Demonstrate JSON POST</h3>
 <textarea name="id" id="txtPOST" class="id" rows="4" cols="50"></textarea>
 <input id="btnSubmitPOST" type="submit" value="POST" />
</body>
</html>

The submission is just JSON in a textarea sent directly to the client:
Fig 5: JSON Post Form

and a demonstration via Robomongo that the data was indeed persisted:
Fig 6: JSON Results

The last two approaches are also shown below in Python:
import requests 

# STATUS CODES:
#  <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html>
#  200 = OK
#  201 = Created

def post2():
 payload = { 'author' : 'p5-author', 'id' : '9995', 'title' : 'p5-title' }
 headers = {u'content-type': u'application/x-www-form-urlencoded'} 
 r = requests.post("http://host:8080/blogger/demo/endpoint/form", params=payload, headers=headers) 
 test(r.status_code)
 return r.text

def post3():
 r = requests.post( 'http://host:8080/blogger/demo/endpoint/{"author":"this is my content","title":"my title","id":9956}' )
 test(r.status_code)
 return r.text

def test(status_code):
 print 'status_code:', status_code
 assert (200 == status_code or 201 == status_code)

p5();
p6()



References

  1. [GitHub] Source Code for this article [or, Google Drive for a zip file]
  2. Resources:
    1. GitHub] Apache Tomcat 7 (configured for this blog post)
    2. [Docker Register] MongoDB Dockerfile
  3. HOWTO Articles:
    1. [Blog] Setting up Apache Tomcat and Docker
    2. [Blog] Working with MongoDB (Installation and Usage)
    3. [Blog] Using Maven for Build Automation

Friday, January 9, 2015

Using Spring JPA

Introduction


Spring JPA is a quick method for providing CRUD functionality around an RDBMS.

Using Spring JPA, I can define an @Entity and then create a @Repository interface that takes the persisted entity as a generic type.  

In the repository interface, I define method names, like this:
 List<Gngram> findByCollectionNameAndFocalTermAndFocalPos(Pageable pageable, String collectionName, String focalTerm, String focalPos);   

The implementation is handled automagically by Spring.

I don't have to write a single line of SQL, JQL, or any query language.  This allows us to get basic CRUD functionality around any relational table or view in a matter of minutes.


In this example, we'll be using this table:


This table holds a Google n-Gram.  The format of each record is explained here.  As each record is read from a file, the database is updated.


Outline




Environment

  1. JDK 7
  2. Maven 3.2.3
  3. Spring
    1. Context, Core, JDBC 4.1.3
    2. Data-Commons and Data-JPA 4.1.4
  4. MySQL 5.1.27
JAR dependencies are specified in Maven and the exact version numbers given here:


The POM

My POM file looks like this:
 <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/xsd/maven-4.0.0.xsd">  
      <modelVersion>4.0.0</modelVersion>  
   
      <groupId>com.mycompany.app</groupId>  
      <artifactId>mycompany-app</artifactId>  
      <version>4.1.4</version>  
      <packaging>pom</packaging>  
   
      <properties>  
           <spring-context.version>4.1.3.RELEASE</spring-context.version>  
           <spring-core.version>4.1.3.RELEASE</spring-core.version>  
           <spring-jdbc.version>4.1.3.RELEASE</spring-jdbc.version>  
           <spring-data-commons-core.version>1.4.1.RELEASE</spring-data-commons-core.version>  
           <spring-data-jpa.version>1.7.1.RELEASE</spring-data-jpa.version>  
           <hibernate-entitymanager.version>4.3.7.Final</hibernate-entitymanager.version>  
      </properties>  
   
      <dependencies>  
   
           <!-- Spring -->  
           <dependency>  
                <groupId>org.springframework</groupId>  
                <artifactId>spring-context</artifactId>  
                <version>${spring-context.version}</version>  
           </dependency>  
           <dependency>  
                <groupId>org.springframework</groupId>  
                <artifactId>spring-core</artifactId>  
                <version>${spring-core.version}</version>  
           </dependency>  
           <dependency>  
                <groupId>org.springframework</groupId>  
                <artifactId>spring-jdbc</artifactId>  
                <version>${spring-jdbc.version}</version>  
           </dependency>  
           <dependency>  
                <groupId>org.springframework.data</groupId>  
                <artifactId>spring-data-commons</artifactId>  
                <!--version>1.6.3.RELEASE</version-->  
                <version>1.9.1.RELEASE</version>  
   
           </dependency>  
           <dependency>  
                <groupId>org.springframework.data</groupId>  
                <artifactId>spring-data-jpa</artifactId>  
                <version>1.7.1.RELEASE</version>  
           </dependency>  
           <dependency>  
                <groupId>org.hibernate</groupId>  
                <artifactId>hibernate-entitymanager</artifactId>  
                <version>4.3.1.Final</version>  
           </dependency>  
   
           <!-- MySQL -->  
           <dependency>  
                <groupId>mysql</groupId>  
                <artifactId>mysql-connector-java</artifactId>  
                <version>5.1.27</version>  
           </dependency>  
             
           <!-- Commons JDBC -->  
           <dependency>  
                <groupId>commons-pool</groupId>  
                <artifactId>commons-pool</artifactId>  
                <version>20030825.183949</version>  
           </dependency>  
           <dependency>  
                <groupId>commons-dbcp</groupId>  
                <artifactId>commons-dbcp</artifactId>  
                <version>20030825.184428</version>  
           </dependency>  
           <dependency>  
                <groupId>commons-collections</groupId>  
                <artifactId>commons-collections</artifactId>  
                <version>20040616</version>  
           </dependency>  
   
      </dependencies>  
   
 </project>  

The groupId and artifactId in red text above should be changed.


The Application Context

The primary configuration is in the application-context.xml file for the project. This configuration file provides the context for the Spring IoC container.

My context file looks like this:
 <?xml version="1.0" encoding="UTF-8"?>  
 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:repository="http://www.springframework.org/schema/data/repository" xmlns:tx="http://www.springframework.org/schema/tx"  
      xsi:schemaLocation="http://www.springframework.org/schema/data/repository http://www.springframework.org/schema/data/repository/spring-repository-1.6.xsd  
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
           http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd  
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd  
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">  
   
      <context:annotation-config />  
      <jpa:repositories base-package="org.swtk.ngrams.google.db.data.repositories" />  
      <context:component-scan base-package="org.swtk.ngrams.google.db.data" />  
   
      <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">  
           <property name="dataSource" ref="datasource" />  
           <property name="persistenceUnitName" value="swtk-gngrams" />  
           <property name="packagesToScan" value="org.swtk.ngrams.google.db" />  
           <property name="jpaVendorAdapter">  
                <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">  
                     <property name="showSql" value="false" />  
                     <property name="database" value="MYSQL" />  
                </bean>  
           </property>  
      </bean>  
   
      <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">  
           <property name="entityManagerFactory" ref="entityManagerFactory" />  
      </bean>  
   
      <bean id="datasource" class="org.apache.commons.dbcp.BasicDataSource">  
           <property name="url" value="jdbc:mysql://127.0.0.1:3306/gngrams" />  
           <property name="username" value="userid" />  
           <property name="password" value="password" />  
           <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
      </bean>  
   
 </beans>  

The text highlighted in red should be changed.


Package Layout


I use the following package layout:

  • com.mycompany.app.data
    • entities
    • repositories
    • service

The entities package contains the persistent POJOs that map to tables in the database.  The repositories package contains granular methods for accessing data in the database.  The service package contains coarser-grained methods for accessing data; perhaps using multiple methods from multiple repositories and returning a combined result to a consumer.


Entities

My entity class reflects the table in the image at the start of this article.

To any developer already familiar with ORM solutions, the annotations on this POJO should be straightforward:
 package org.swtk.ngrams.google.db.data.entities;  
   
 import javax.persistence.Column;  
 import javax.persistence.Entity;  
 import javax.persistence.GeneratedValue;  
 import javax.persistence.GenerationType;  
 import javax.persistence.Id;  
 import javax.persistence.Table;  
   
 import org.swtk.common.core.util.StringUtils;  
   
 @Entity  
 @Table(name = "gngram")  
 public class Gngram {  
   
      /**  
       *     Purpose:  
       *     The name of the collection file  
       *  
       *     eg. this is typically the underlying source file that contains the google n-grams  
       */  
      @Column(name = "COLLECTION_NAME")  
      private String     collectionName;  
   
      /**  
       *     Purpose:  
       *     The term with an attached part-of-speech  
       *  
       *     eg. given this n-Gram:  
       *          "had well_ADV organized"  
       *     the focal pos is  
       *          "ADV"  
       */  
      @Column(name = "FOCAL_POS")  
      private String     focalPos;  
   
      /**  
       *     Purpose:  
       *     The term with an attached part-of-speech  
       *  
       *     eg. given this n-Gram:  
       *          "had well_ADV organized"  
       *     the focal term is  
       *          "well"  
       */  
      @Column(name = "FOCAL_TERM")  
      private String     focalTerm;  
   
      /**  
       *     Purpose:  
       *     The primary key   
       */  
      @Id  
      @Column(name = "ID")  
      @GeneratedValue(strategy = GenerationType.IDENTITY)  
      private Integer     id;  
   
      /**  
       *     Purpose:  
       *     The number of times the n-Gram was found (across the entire collection)  
       */  
      @Column(name = "MATCH_COUNT")  
      private Double     matchCount;  
   
      /**  
       *     Purpose:  
       *     The number of volumes the n-Gram was found in  
       */  
      @Column(name = "VOLUME_COUNT")  
      private Double     volumeCount;  
   
      public String getCollectionName() {  
           return collectionName;  
      }  
   
      public String getFocalPos() {  
           return focalPos;  
      }  
   
      public String getFocalTerm() {  
           return focalTerm;  
      }  
   
      public Integer getId() {  
           return id;  
      }  
   
      public Double getMatchCount() {  
           return matchCount;  
      }  
   
      public Double getVolumeCount() {  
           return volumeCount;  
      }  
   
      public boolean hasFocalPos() {  
           return StringUtils.hasValue(getFocalPos());  
      }  
   
      public boolean hasFocalTerm() {  
           return StringUtils.hasValue(getFocalTerm());  
      }  
   
      public void setCollectionName(String collectionName) {  
           this.collectionName = collectionName;  
      }  
   
      public void setFocalPos(String focalPos) {  
           this.focalPos = focalPos;  
      }  
   
      public void setFocalTerm(String focalTerm) {  
           this.focalTerm = focalTerm;  
      }  
   
      public void setId(Integer id) {  
           this.id = id;  
      }  
   
      public void setMatchCount(Double matchCount) {  
           this.matchCount = matchCount;  
      }  
   
      public void setVolumeCount(Double volumeCount) {  
           this.volumeCount = volumeCount;  
      }  
 }  
   


Repositories

The Repository interface extends the JpaRepository interface, and takes the persistent entity as a generic type:

 package org.swtk.ngrams.google.db.data.repositories;  

 import java.util.List;  
 import org.springframework.data.domain.Pageable;  
 import org.springframework.data.jpa.repository.JpaRepository;  
 import org.springframework.stereotype.Repository;  
 import org.swtk.ngrams.google.db.data.entities.Gngram;  

 @Repository  
 public interface GngramRepository extends JpaRepository<Gngram, Integer> {  

      List<Gngram> findByCollectionName(Pageable pageable, String collectionName);  

      List<Gngram> findByCollectionNameAndFocalPos(Pageable pageable, String collectionName, String focalPos);  

      List<Gngram> findByCollectionNameAndFocalTerm(Pageable pageable, String collectionName, String focalTerm);  

      List<Gngram> findByCollectionNameAndFocalTermAndFocalPos(Pageable pageable, String collectionName, String focalTerm, String focalPos);  
 }  

The pageable parameter allows the specification of the page and number of results per page.  The remaining parameters are inserted into the query generated by Spring for each of these method implementations.


Test Case

The test case looks like this:
 package org.swtk.ngrams.google.db.data.repositories;  
   
 import static org.junit.Assert.assertEquals;  
 import static org.junit.Assert.assertNotNull;  
   
 import java.util.ArrayList;  
 import java.util.List;  
   
 import org.junit.Test;  
 import org.swtk.common.core.env.LogManager;  
 import org.swtk.ngrams.google.db.data.entities.Gngram;  
 import org.swtk.ngrams.google.db.util.adapter.GngramAdapter;  
 import org.swtk.ngrams.ioc.NgramsIocContainer;  
   
 public class GngramRepositoryTest {  
   
      public static LogManager     logger               = new LogManager(GngramRepositoryTest.class);  
   
      private static final String     COLLECTION_NAME     = "test-run-grt";  
   
      private void createAndSave(GngramRepository gngramRepository) throws Throwable {  
           List<Gngram> list = new ArrayList<Gngram>();  
   
           list.add(GngramAdapter.transform(COLLECTION_NAME, "had well_ADV organized     1904     11     11"));  
           list.add(GngramAdapter.transform(COLLECTION_NAME, "had well_NOUN organized     1934     22     22"));  
   
           gngramRepository.save(list);  
      }  
   
      @Test  
      public void run() throws Throwable {  
   
           GngramRepository gngramRepository = NgramsIocContainer.getGngramsContext().getBean(GngramRepository.class);  
           assertNotNull(gngramRepository);  
   
           /* clear database: test prep */  
           gngramRepository.delete(gngramRepository.findByCollectionName(null, COLLECTION_NAME));  
   
           /* create and save the entity */  
           createAndSave(gngramRepository);  
   
           /* test basic retrieval methods */  
           assertEquals(2, gngramRepository.findByCollectionNameAndFocalTerm(null, COLLECTION_NAME, "well").size());  
           assertEquals(1, gngramRepository.findByCollectionNameAndFocalPos(null, COLLECTION_NAME, "ADV").size());  
   
           /* clear database: test cleanup */  
           gngramRepository.delete(gngramRepository.findByCollectionName(null, COLLECTION_NAME));  
      }  
 }  


I don't have a services class defined for this example.


References

  1. Official Spring JPA Documentation
    1. At some point, every Spring JPA developer should read through this documentation completely.
    2. Supported Keywords Inside Method Names
      1. The complete list of keywords and samples.  
      2. Essentially an EBNF-like table for writing methods in your interface.
  2. Using Custom Queries with Query Methods
    1. Generic queries can only take you so far.
    2. [Stackoverflow] Custom Methods belong in separate interfaces
      1. Putting a @Query annotation over a method in an interface that extends JpaRepository doesn't seem to work as well as some articles suggest.

Monday, January 5, 2015

An Introduction to Java API for RESTful Web Services (JAX-RS)

Environment
  1. Eclipse Luna Service Release 1 (4.4.1)
  2. JDK 1.7.0_71
  3. Apache Maven 3.2.3 
  4. Tomcat 7


Outline
  • Configure a Maven Web Project in Eclipse
  • Create a REST resource
  • Deploy to Tomcat



Creating the Project


If you have a preferred method for creating Dynamic Web Projects in Eclipse, you may find it simpler to skip directly to the POM (below).

1. Select
File > New > Maven Project



2a. Check Create a simple project (skip archetype selection)

2b. Uncheck Use default Workspace location and enter a suitable path for the project


3. Fill out the required information for a new Maven Project
4. Click Finish


Note: Although we'll change this to WAR later, it's important to keep the packaging as a JAR file in the dialog for now.  This will trigger the configuration event for adding a web.xml file down below.

Configuring the Project


1. Right-Click the Project > Properties > Project Facets
Select the option to convert this project to "faceted form":


2a. Ensure that "Dynamic Web Module 3.0" is selected
2b. Ensure that "Java 1.7" is selected


3. Click the link at the bottom of the dialog that says "Further configuration available".
Check the box that says "Generate web.xml deployment descriptor":
4. Click Ok


Edit the POM


Replace (or merge) the generated POM with the file given below, and then update the Eclipse Project:
Right Click on the Project > Maven > Update Project

Run the Maven Clean Package command from the command line:
mvn clean package -e

My POM file looks like this:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<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/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>com.mycompany.app</groupId>
 <artifactId>mycompany-app</artifactId>
 <version>1.0.0</version>
 <packaging>war</packaging>

 <url />
 <name>mycompany-app</name>
 <inceptionYear>2015</inceptionYear>
 <description>This is my company's app</description>

 <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>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.4</version>
    <configuration>
     <warSourceDirectory>WebContent</warSourceDirectory>
     <failOnMissingWebXml>false</failOnMissingWebXml>
    </configuration>
   </plugin>

   <!-- Required for Maven-to-Tomcat Deployments -->
   <plugin>
    <groupId>org.apache.tomcat.maven</groupId>
    <artifactId>tomcat7-maven-plugin</artifactId>
    <version>2.2</version>
    <configuration>
     <url>http://192.168.1.5:8080/manager/text</url>
     <server>TomcatServer</server>
     <path>/test</path>
     <username>tomcat</username>
     <password>tomcat</password>
    </configuration>
   </plugin>

  </plugins>
 </build>

 <repositories>
  <repository>
   <id>maven2-repository.java.net</id>
   <name>Java.net Repository for Maven</name>
   <url>http://download.java.net/maven/2/</url>
   <layout>default</layout>
  </repository>
 </repositories>

 <dependencies>
  <dependency>
   <groupId>com.sun.jersey</groupId>
   <artifactId>jersey-server</artifactId>
   <version>1.9</version>
  </dependency>
  <dependency>
   <groupId>com.owlike</groupId>
   <artifactId>genson</artifactId>
   <version>1.2</version>
  </dependency>
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>javax.servlet-api</artifactId>
   <version>3.1.0</version>
  </dependency>
 </dependencies>

</project>
 


POM Overview:

  1. Application Packaging
    1. Must be a Web Application Archive (WAR) file
  2. Tomcat Plugin
    1. The configuration for this plugin, and for the Tomcat server, is detailed in this article.
  3. Additional Dependencies
    1. Jersey and the Genson JSON conversion library.
    2. The Javax Servlet package (optional for this tutorial, but usually necessary when additional code is added).


You should now have an error-free Eclipse Workspace:



Configuring the Deployment Description (web.xml)


If you didn't generate a deployment descriptor when you created the project in Eclipse, add it under this path:
mycompany-app\WebContent\WEB-INF\web.xml


My web.xml file looks like this:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
 id="WebApp_ID" version="3.0">

 <display-name>RESTful Web App</display-name>

 <servlet>
  <servlet-name>jersey-helloworld-serlvet</servlet-name>
  <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
  <init-param>
   <param-name>com.sun.jersey.config.property.packages</param-name>
   <param-value>com.mycompany.app</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
  <servlet-name>jersey-helloworld-serlvet</servlet-name>
  <url-pattern>/rest/*</url-pattern>
 </servlet-mapping>

</web-app>  

You might want to consider modifying the values highlighted in red above.

  1. Jersey Package Configuration value
    1. Specifies the package structure containing the REST resources.
  2. URL pattern
    1. This is a resource mapping



Creating the REST Resource


Outline:
  1. Create a User POJO
  2. Create the REST Service
We'll populate the User POJO with values passed into the REST service, and return the User object as JSON.


The User POJO
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
 package com.mycompany.app;  
   
 public class User {  
   
      private Integer age;  
   
      private String firstname;  
   
      private String lastname;  
   
      public User() {  
      }  
   
      public User(String firstname, String lastname, Integer age) {  
           setFirstname(firstname);  
           setLastname(lastname);  
           setAge(age);  
      }  
   
      public Integer getAge() {  
           return age;  
      }  
   
      public String getFirstname() {  
           return firstname;  
      }  
   
      public String getLastname() {  
           return lastname;  
      }  
   
      public void setAge(Integer age) {  
           this.age = age;  
      }  
   
      public void setFirstname(String firstname) {  
           this.firstname = firstname;  
      }  
   
      public void setLastname(String lastname) {  
           this.lastname = lastname;  
      }  
 }  


The REST Service
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.swtk.sandbox.rest;  
   
 import javax.ws.rs.DefaultValue;  
 import javax.ws.rs.GET;  
 import javax.ws.rs.Path;  
 import javax.ws.rs.Produces;  
 import javax.ws.rs.QueryParam;  
 import javax.ws.rs.core.MediaType;  
   
 @Path("/simpleREST")  
 public class SimpleREST {  
   
      @GET  
      @Path("/user")  
      @Produces(MediaType.APPLICATION_JSON)  
      public User responseMsg(  
                @DefaultValue("john") @QueryParam("firstname") String firstname,  
                @DefaultValue("doe") @QueryParam("lastname") String lastname,  
                @DefaultValue("21") @QueryParam("age") Integer age) {  
   
           return new User(firstname, lastname, age);  
      }  
 }  

In order to construct a URL to test this service, we need these three items:

  1. The Tomcat deployment path from the POM file:  /sandbox-rest
  2. The url-pattern from the servlet-mapping in the web.xml file: /rest
  3. The @Path annotation on the REST class:  /simpleREST
  4. The @Path annotation on the GET method in this class:  /user

We assemble these three together and get:
http://localhost:8080/sandbox-rest/rest/simpleREST/user



Deploying the Resource


Assuming both the POM file and Tomcat are configured correctly, this will be as simple as typing:
mvn tomcat7:deploy -e

To undeploy the web-app, simply type:
mvn tomcat7:undeploy -e

Using this URL
http://localhost:8080/sandbox-rest/rest/simpleREST/user

the web-app returns:



References

  1. Deploying a Web Project with Maven
    1. Describes the POM configuration for automated Maven-to-Tomcat deployments.
  2. [JavaCodeGeeks] Jersey Hello World Example
    1. The first section of my post (using Eclipse to create a Maven Web Project) was based on this tutorial.