Saturday, 22 March 2014

Java Tutorial 3 - Simple Servlet and Remote Repository

On the third day of your office life, you will create a simple servlet and remote repository. SVN and GIT are not equally popular in developer world but we will use GIT in this tutorial as it is distributed version control system.

Prerequisite

Completed Java Tutorial Part 2.

Setup Git Repository

1. Create GitHub account

GitHub provide public and free Git repository. If you are a developer and do not have GitHub account yet, go to GitHub website and create one.

I am going to put all of the related stuffed of this blog to

https://github.com/tuanngda/sgdev-blog.git

If you are using Linux or Mac, you may already have Linux as part of OS installation, if you use Window, follow instruction in the website and install git bash.

2. Create Git Repo for project

Git repository require your repository folder to have identical name with the repository name. As the repository I created before named sgdev-blog, I rename my workspace to identical name so that I can sync workspace to GitHub.

I did that because I want to share all project in a single repository. If you choose to create one repository for each of your project, it is not necessary. However, if you choose to do the same way, then kindly rename workspace to fit your repository name.

Use the console in Linux/Mac or Git Bash in Window, go to java/{workspaceName}

Type following command:

$ git init
--> This command is to create a local repository in the current folder. It will create a .git folder that contain this local repository. As our workspace is going to contain several projects, creating repository in workspace folder let us share single repository for several projects, which is good.

$ git status
--> This command show current status of projects, which files/folders have not been checked in.

$ git add sample_webapp
--> This command add everything under current folder to git repository. This is actually very bad practice to add project setting files to repository as it is environment specific, we will not do it any more after learning Maven.

$ git commit -m 'First Commit'
--> Git add command added the files to staged status, which indicate these file are supposed to be commit. Git commit command commit all  the staged file with a message.

$ git remote add origin https://github.com/tuanngda/sgdev-blog.git
$ git push origin master
--> This is the most tricky part of distributed repository. You have a local repository, which store inside .git folder. You also have a remote repository, stored in GitHub. You commit to your local repository and sync your local repository to remote repository. This sound troublesome but it helps you to continue your work when GitHub is down. Your local repository is identical to remote repository, just find another place to use as remote repository.

You suppose to see your webapp in GitHub repository after completing these steps.

Create Simple Servlet

At the end of the earlier tutorial, we have created and deployed one webapp to Tomcat. Sadly, this webapp has only one html page, which does not justify why we need Java. Now we will need to make it smarter by adding a simple servlet into it.

1. Goals

As a boring developer, I want to create a local, minimal Twitter like application so that user can post and view their current twits.

Look at above, it is an user story. This is a very popular practice in the IT world today, when missions were created under the form of user story. The format of user story is

As [role], I want [goals] so that [benefit]

Dilbert.com


It is very concise but it helps to identify 3 concerns:
  • Who will benefit from the task
  • What user think that need to be develop
  • What benefit that user want to have
In the old days, developer did what they are told, which means they only care about goal but not benefit. As things go on, people realize that it is not so effective as sometimes user do not know the best strategies to achieve what they really want. That why, modern development requires developers to actively involve and consult user on whether they are building the right thing.

2. Acceptance Criteria

Together with goals, in the planning meeting, developers will provide estimation plus finalize acceptance criteria. Acceptance Criteria is what will be tested to decide whether you have completed the story.

Assumption:
  • No Authentication required.
  • Each user has and only has one twit.
  • Each user will need to upload twit with one unique user id.
  • No deletion of twit are allowed. Update is achieve by posting new twit with the same user id.
  • The application can only be used in local, no clustering support.

Acceptance Criteria:
  • Have a RestAPI interface for any user view all user twits, specific user twits, upload twits.
  • Show status 404 if user id or twit is not found.
Tasks:
  • Build a simple bean that contains all the Twits.
  • Create a Servlet that serve bean content.
Estimation: 0.5 man day.

3. Create your service.

Create first package as com.blogspot.sgdev_blog. This is convention that base package is reversed naming of internet domain.

Create an interface for the service that we need to implements:

package com.blogspot.sgdev_blog.service;

import java.util.Collection;

import com.blogspot.sgdev_blog.exception.RecordNotFoundException;

public interface TwitService {
 
 public static int MAX_LENGTH = 160;
 
 public String getTwit(String userId) throws RecordNotFoundException;
 
 public void insertTwit(String userId, String text);
 
 public Collection getAllTwits();

}

People still argue that whether do we need interface for each concrete class if the class happen to be the only one that implements the interface. The supporters say interface serve as contract, it allows you to focus on defining the usage of a service first then implement it later. Declaring a service by its interface rather than concrete class make changes easier in the future. While it takes time for you to build your own style, I will build interface for every class.

I will implements it this way

package com.blogspot.sgdev_blog.service.impl;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.blogspot.sgdev_blog.exception.RecordNotFoundException;
import com.blogspot.sgdev_blog.service.TwitService;

public class TwitServiceImpl implements TwitService {
 
 Map twitMap = new ConcurrentHashMap();

 @Override
 public String getTwit(String userId) throws RecordNotFoundException {
  
  String twit = twitMap.get(userId); 
  
  if (twit == null){
   throw new RecordNotFoundException("No twit found for user "+userId);
  }
  
  return twit;
 }

 @Override
 public void insertTwit(String userId, String text) {
  
  if (userId==null || userId.length()==0 
                   || text==null || text.length()==0 || text.length()>MAX_LENGTH){
   throw new RuntimeException("Invalid twit!");
  }
  
  twitMap.put(userId, text);
 }

 @Override
 public Collection getAllTwits() {
  
  return twitMap.values();
 }
}

There is nothing fancy in the code above, no clustering support, no authorization and persistence, but it help to achieve the goal of the story.

4. Create a servlet 

If you have not known about Rest yet, study it. We are going to create a true Restful API here because Twit does not have ID but we will borrow the ideas of GET to retrieve record and POST to upload/update record.

http://www.restapitutorial.com/lessons/whatisrest.html

Restful API can be seen as one effort that representing a major shift in the software design paradigm, convention over configuration. In early day of Java programming, developing software properly means writing as much document as possible, putting as much comments as you can and creating API as specific as possible. After a while, people start to realise that developers spending more time writing documents rather than writing code and creating all kinds of contract everywhere is too tedious.

Finally, everyone feel that that software should be developed with smarter, less painful way. Over the last decade, most of the frameworks were shifted with default configuration, which let developer modify configuration rather than create configuration. Usage of Json is more ambiguous but shorter and easier to extends than XML. More over, developers do not need to create the bulky remote and local interface. With Restful API, you do not need to handover a document to tell people how to use your API. You simply tell them that you will develop RestAPI and hope that they are smart enough to figure out how your API may look like.

Professional developer know their IDE well, for example, this is how you create Servlet:


Depends on your active Perspective, Eclipse give you different values when you click New button. That why I switch over between JavaEE and Java perspective when I change focus from writing service to writing front end.

After choosing package to put your Servlet, Servlet class name, we can continue to choose URL mapping


Continue until the end, Eclipse will give you an empty TwitServlet inside the package you choose and add an entry to your web.xml

TwitServlet

/**
 * Servlet implementation class TwitServlet
 */
public class TwitServlet extends HttpServlet {
 private static final long serialVersionUID = 1L;
       
    public TwitServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  // TODO Auto-generated method stub
 }

 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  // TODO Auto-generated method stub
 }
}

web.xml

  <servlet>
    <description></description>
    <display-name>TwitServlet</display-name>
    <servlet-name>TwitServlet</servlet-name>
    <servlet-class>com.blogspot.sgdev_blog.TwitServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>TwitServlet</servlet-name>
    <url-pattern>/twits</url-pattern>
  </servlet-mapping>

Simple, but good enough. We have a servlet, which is defined by servlet-class. We have the url-pattern that will be served by this servlet. Even better, our stupid servlet know how to handle GET and POST requests.

5. Integrate this servlet with your service

Now, we already have a servlet and a service, and they are totally unrelated. Let integrate it.

In earlier step, Eclipse give us a specific mapping, which does not fit our need. We will change it to extension mapping

<servlet-mapping>
    <servlet-name>TwitServlet</servlet-name>
    <url-pattern>/twits/*</url-pattern>
  </servlet-mapping>

After this, we need to add Gson library to our project to convert complex data type to Json. Simply download and drop the gson-2.2.4.jar to WEB-INF/lib folder. Container will include this folder as source for webapp class loader.

Make use of TwitService to serve contents:

public class TwitServlet extends HttpServlet {
 
 private TwitService twitService;
 
 private Gson gson = new Gson();
       
    public TwitServlet() {
        super();
        twitService = new TwitServiceImpl();
    }

 protected void doGet(HttpServletRequest request, HttpServletResponse response) 
   throws ServletException, IOException {
  String responseBody = "";
  String path = (request.getPathInfo()==null) ? "" : 
   request.getPathInfo().replaceFirst("/", "");
  
  if (path.length()==0){
   Collection twits = twitService.getAllTwits();
   responseBody = gson.toJson(twits);
  }
  else {
   try {
    String userId = path;
    responseBody = twitService.getTwit(userId);
   } catch (RecordNotFoundException e) {
    responseBody = e.getMessage();
    response.setStatus(404);
   }
  }
  
  response.getWriter().write(responseBody);
 }

 protected void doPost(HttpServletRequest request, HttpServletResponse response) 
   throws ServletException, IOException {
  String responseBody = "";
  String path = (request.getPathInfo()==null) ? "" : 
   request.getPathInfo().replaceFirst("/", "");
  
  if (path.length()==0){
   responseBody = "Invalid usage";
   response.setStatus(401);
  }
  else {
   String userId = path;
   String twit = convertStreamToString(request.getInputStream());
   
   twitService.insertTwit(userId, twit);
   responseBody = "Twit inserted";
  }
  
  response.getWriter().write(responseBody);
 }
 
 static String convertStreamToString(java.io.InputStream is) {
     java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
     return s.hasNext() ? s.next() : "";
 }
}

6. Test the webapp with Rest client

Let test this application with one Rest client. As most of us has Chrome installed, let use Postman to test it. Download Postman plugin to Chrome and open it.

Use it to send some twits to our webapp



Discussion & Thinking 


  • The service and Gson object can be shared because they are ThreadSafe. Gson library is already ThreadSafe. The TwitService is thread safe as well because the only shared object, twitMap is concurrent hashmap. This is a normal map except that every operation is synchronized.
  • The twitService does not need to be static field variable because by specification, container will only create one servlet. That mean any request hit this URL will be served by the same instance of Servlet.