Wednesday, 7 August 2013

Strongly consistent web application in Google App Engine

Strongly consistent application is an application whose database writings will be reflected immediately in the database queries. In the case of eventually consistent applications, it is not necessary that the recently stored data be reflected in the immediate database queries.

Google App engine provides both strongly and eventually consistent modes for their applications. By default all the Google App engine applications are strongly consistent provided they should use ancestor queries in the applications.

A query that fetches data from a single entity group is called ancestor query. Entity group is a group of entities who share same parent key.

In this application we will develop a Java based web application whose database writings will be in strongly consistent mode. In this application, we are using low level datastore api for database operations.

We are developing this application using Eclipse 4.2.0. Please ensure that, you have installed Google Plugin for Eclipse. If you have not installed Google Plugin for Eclipse, you can follow this article for Google Plugin for Eclipse.

Some of the screenshots of this application are given below :

Lists of all the products added

Add Form


Now we are going to see, how to develop this application in step by step.


1. Create new Web Application Project

File -> New -> Web Application Project



Note : Ensure that, the checkboxes "Use Google Web Toolkit" and "Generate project sample code" are unchecked.

2. Create a css file namely main.css in the directory inventory/war/css


inventory/war/css/main.css
form,table {
 background: -webkit-gradient(linear, bottom, left 175px, from(#CCCCCC), to(#EEEEEE));
 background: -moz-linear-gradient(bottom, #CCCCCC, #EEEEEE 175px);
 margin:auto;  
 font-family: Tahoma, Geneva, sans-serif;
 font-size: 14px;
 font-style: italic;
 line-height: 24px;
 font-weight: bold;
 color: #09C;
 text-decoration: none;
 -webkit-border-radius: 10px;
 -moz-border-radius: 10px;
 border-radius: 10px;
 padding:10px;
 border: 1px solid #999;
 border: inset 1px solid #333;
 -webkit-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
 -moz-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
 box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
}

form{
 position:relative;
 width:400px;
 height:400px;
}

table {
 min-width:450px; 
}

tr,td{
 border : 1px solid #999; 
}

input ,textarea   {
 width:375px;
 display:block;
 border: 1px solid #999;
 height: 25px;
 -webkit-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
 -moz-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
 box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
}

textarea#remarks {
 width:375px;
 height:150px;
}

textarea {
 display:block;
}

button {
 width:140px;
 position:absolute; 
 bottom:20px;
 background:#09C;
 color:#fff;
 font-family: Tahoma, Geneva, sans-serif;
 height:30px;
 -webkit-border-radius: 15px;
 -moz-border-radius: 15px;
 border-radius: 15px;
 border: 1p solid #999;
}

button:hover {
 background:#fff;
 color:#09C;
}

button#cancel{
 right:60px;
}

button#submit{
 right:220px;
}

textarea:focus, input:focus {
 border: 1px solid #09C;
}

#add_link {
 position: relative;
 left:120px
}


3. Create a javascript file namely main.js in the directory inventory/war/js

inventory/war/js/main.js
function home(){
 window.location = "/productslist.jsp"
}


4. Create a jsp file namely productslist.jsp in the directory inventory/war

This file displays a list of all the added products.

inventory/war/productslist.jsp
<%@page import="com.google.appengine.api.datastore.FetchOptions"%>
<%@page import="com.google.appengine.api.datastore.DatastoreServiceFactory"%>
<%@page import="com.google.appengine.api.datastore.DatastoreService"%>
<%@page import="com.google.appengine.api.datastore.Query"%>
<%@page import="com.google.appengine.api.datastore.KeyFactory"%>
<%@page import="com.google.appengine.api.datastore.Key" %>
<%@page import="com.google.appengine.api.datastore.Entity" %>
<%@page import="java.util.List" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>


<html>
<head>
<link rel="stylesheet" href="css/main.css"></link>
</head>
<body>
<%
 String productCategory = "general";

 // Parent key for entity group
 Key productCategoryKey = KeyFactory.createKey("ProductCategory", productCategory);
 
 // An ancestor query
 Query query = new Query("Product",productCategoryKey);
 
 // Getting datastore service for querying datastore
 DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
   
 // Fetching entities from datastore
 List<Entity> products = datastore.prepare(query).asList(FetchOptions.Builder.withDefaults()); 
 %>
 
 <!-- Displaying fetched entities in a table format -->
 <table>
 <caption>List of Products<span id="add_link"><a href="/product.jsp?action=add">Add Product</a></span></caption>
 <tr>
  <th>Sl.No</th><th>Code</th><th>Name</th><th>Remarks</th><th></th><th></th>
 </tr>
 
 <%
 int i=0;
 for(Entity product : products){
  pageContext.setAttribute("product_id", product.getKey().getId());
  pageContext.setAttribute("product_code", product.getProperty("code"));
  pageContext.setAttribute("product_name", product.getProperty("name"));
  pageContext.setAttribute("product_remarks", product.getProperty("remarks"));
  i++;
 %>
  <tr>
   <td><%= i %></td>
   <td>${fn:escapeXml(product_code)}</td>
   <td>${fn:escapeXml(product_name)}</td>
   <td>${fn:escapeXml(product_remarks) }</td>
   <td><a href="/product.jsp?id=${product_id }&action=edit">Edit</a></td>
   <td><a href="/product?id=${product_id }&action=del">Delete</a></td>
  </tr> 
 <%
 } 
%>
 </table>
</body>
</html>


5. Create a jsp file namely product.jsp in the directory inventory/war


inventory/war/product.jsp
<%@page import="com.google.appengine.api.datastore.FetchOptions"%>
<%@page import="com.google.appengine.api.datastore.DatastoreServiceFactory"%>
<%@page import="com.google.appengine.api.datastore.DatastoreService"%>
<%@page import="com.google.appengine.api.datastore.Query"%>
<%@page import="com.google.appengine.api.datastore.KeyFactory"%>
<%@page import="com.google.appengine.api.datastore.Key" %>
<%@page import="com.google.appengine.api.datastore.Entity" %>
<%@page import="com.google.appengine.api.datastore.EntityNotFoundException" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>


<html>
<head>
<link rel="stylesheet" href="css/main.css"></link>
<script language="javascript" src="js/main.js"></script>
</head>
<body>
<%
 String action = request.getParameter("action");
 pageContext.setAttribute("action", action);
 
 String formTitle = "Add Product:";
 String buttonTitle = "Add Product"; 

 if(action.equals("edit")){ // For editing existing entity
  
  formTitle = "Edit Product:";
  
  buttonTitle = "Update Product";  
  
  String productCategory = "general";

  String id = request.getParameter("id");
 
 
  // Parent key
  Key productCategoryKey = KeyFactory.createKey("ProductCategory", productCategory); 
  
  // Key of entity to be edited  
  Key key = KeyFactory.createKey(productCategoryKey,"Product", Long.parseLong(id));
  
  // Getting datastore service
  DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
  
  try {
   
   // Fetching the entity to be edited
   Entity product = datastore.get(key);
   
   pageContext.setAttribute("product_id", key.getId());
   pageContext.setAttribute("product_code", product.getProperty("code"));
   pageContext.setAttribute("product_name", product.getProperty("name"));
   pageContext.setAttribute("product_remarks", product.getProperty("remarks"));  
   
   
  } catch (EntityNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
 
 pageContext.setAttribute("form_title", formTitle);
 pageContext.setAttribute("button_title", buttonTitle);
 
 
%>
<form action="/product" method="post">
 <div>
  <h1>${form_title}</h1>
  <label>
   <input type="hidden" name="id" value="${product_id}" />
   <input type="hidden" name="action" value="${action}" />
   <span>Product Code</span>
   <input type="text" name="code" value="${fn:escapeXml(product_code)}"  />
  </label>
  <label>
   <span>Product Name</span>
   <input type="text" name="name" value="${fn:escapeXml(product_name)}" />
  </label>  
  <label>
   <span>Remarks</span>
   <textarea id="remarks" name="remarks">${fn:escapeXml(product_remarks)}</textarea>
   <button type="submit" id="submit" >${button_title}</button>
   <button type="button" id="cancel" onclick="home()" >Cancel</button>
  </label>
 </div>
</form>
</body>
</html>

6. Create a java class namely ProductServlet.java in the directory inventory/src/in/wptrafficanalyzer/inventory



inventory/src/in/wptrafficanalyzer/inventory/ProductServlet.java
package in.wptrafficanalyzer.inventory;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

public class ProductServlet extends HttpServlet{
 
 // Name of parent entity
 private final String productCategoryKind = "ProductCategory";
 private final String productCategory = "general";
 
 private final String productKind = "Product";
 
 // Get Parent Key
 private Key getParentKey(){
  Key parentKey = KeyFactory.createKey(productCategoryKind, productCategory);
  return parentKey;
 }
 
 @Override
 protected void doGet(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
  
  String action = req.getParameter("action");
  String id = req.getParameter("id");
  
  if(action.equals("del")){      
   
   // Delete the entity
   delProduct(id);
   
   // Redirecting to the products lists
   resp.sendRedirect("/productslist.jsp");
   
  } 
 }
 
 
 @Override
 protected void doPost(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
  
  String action = req.getParameter("action");
  
  String id = req.getParameter("id");
  
  // Get the code entered in the form
  String code = req.getParameter("code");
  
  // Get the name entered in the form
  String name = req.getParameter("name");
  
  // Get the remarks entered in the form
  String remarks = req.getParameter("remarks");
  
  if(action.equals("add")){ 
   // Add new entity
   addProduct(code, name, remarks);
  }else if(action.equals("edit")){
   // Update existing entity
   updateProduct(id,code,name,remarks);
  }
  
  // Redirecting to the products lists
  resp.sendRedirect("/productslist.jsp");  
 }
 
 
 private void addProduct(String code,String name,String remarks){
  // Getting datastore service
  DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 
    
  // Creating a product entity
  Entity product = new Entity("Product", getParentKey());  
    
  product.setProperty("code", code);
  product.setProperty("name", name);
  product.setProperty("remarks", remarks);
    
  // Storing the new entity to the datastore
  datastore.put(product);
  
 }
 
 // Update the existing entity
 private void updateProduct(String id,String code,String name,String remarks){    
  
  // Getting key of the existing entity
  Key key = KeyFactory.createKey(getParentKey(),"Product", Long.parseLong(id));
  
  // Getting datastore service
  DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
  
  try {
   Entity product = datastore.get(key);
   product.setProperty("code", code);
   product.setProperty("name", name);
   product.setProperty("remarks", remarks);
   
   // Overwrite the existing entity
   datastore.put(product);
   
  } catch (EntityNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
 } 
 
 // Delete the entity
 private void delProduct(String id){
  Key key = KeyFactory.createKey(getParentKey(),"Product", Long.parseLong(id));
  
  // Getting datastore service
  DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
  
  // Delete the entity
  datastore.delete(key);  
 }
 
}


7. Updating the file application descriptor file web.xml in the directory war/WEB-INF




inventory/war/WEB-INF/web.xml


    
        product
        in.wptrafficanalyzer.inventory.ProductServlet        
    
    
    
        product
        /product
    
    
    
        
            productslist.jsp
        
    
  

8. Start the local Google App Engine server

In eclipse, click the menu "Run -> Debug as -> Web Application". Now the local Google App Engine server will be started in the port 8888.

9. Open the application

In a web browser, enter the address http://localhost:8888. Now a web page , with list of products will be opened. Click on "Add Product" link to add new products to the list.


10. Deploying this application

Please refer this article to deploy this application to the real world Google App Engine.



No comments:

Post a Comment