Google

Nov 29, 2012

CORS and jQuery with Spring MVC, RESTful Web Service and Maven

In the post JSONP and jQuery with Spring MVC, RESTful Web Service and Maven we looked at cross domain example with JSONP. JSONP has a number of limitations like, it supports only GET requests and not PUT, POST, DELETE, etc and it does not also send headers across. CORS stands for Cross Origin Resource Sharing, which allows you to share GET, POST, PUT, and DELETE requests and CORS is supported by the modern browsers.The CORS make use of 2 requests.

Request 1: "OPTIONS" request as part of the handshake to determine if cross domain is allowed by the server.

Request 2: GET, POST, PUT, or DELETE request that performs the actual operation on the server.




This example basically extends the JSONP example with only the changes shown here.


Step 1: The JSP file with jQuery, JavaScript, and ajax code snippets. This code snippet also shows extraction of the request headers and populating back the response.


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  
 <script type="text/javascript" src="/secure/aes-gui/resources/js/jquery/jquery-1.7.2.js"></script>
 
 <title>Spring MVC - CORS and jQuery Tutorial</title>
</head>
<body>
 
<h3>Spring MVC - CORS and jQuery Tutorial</h3>

<div style="border: 1px solid #ccc; width: 600px;">
 Add Two Numbers:
 
 <input id="inputNumber1" name="inputNumber1" type="text" size="5" > +
 <input id="inputNumber2" name="inputNumber2" type="text" size="5">
 <input type="submit" value="Add" onclick="add()" />
 
 Sum: <span id="sum"></span>
 
 
 <input type="submit" value="Show Headers" onclick="displayHeaders()" />
 
 
</div>
 
 
<script type="text/javascript">

 function add() {
  var url = 'http://DEV:8080/aes-gui/simple/poc/main/add';
  
  console.log("logging...............");
  
  $.ajax({
   type : 'GET',
   url : url,
   data : {
    inputNumber1 : $("#inputNumber1").val(),
    inputNumber2 : $("#inputNumber2").val()
   },
   async : false,
   contentType : "application/json",
   crossDomain : true,
   success : function(response, textStatus, jqXHR) {
    console.log("reached here");
    // data contains the result
    // Assign result to the sum id
    $("#sum").replaceWith('<span id="sum">' + response + '</span>');
    console.log(response);

   },
   headers: headerData,
    
   error : function(jqXHR, textStatus, errorThrown) {
    console.log(errorThrown);
   }
  });
 };
 
 function displayHeaders() {
  var req = new XMLHttpRequest();
  req.open('GET', document.location, false);
  req.send(null);
  var headers = req.getAllResponseHeaders().toLowerCase();
  console.log(headers);
     
  var splitHeaders = headers.split(/\r\n|\n|\r/g);
  console.log("split headers:\n"  + splitHeaders);
  for (var i in splitHeaders){
   var currVal = splitHeaders[i];
   var entry = currVal.split(":");
   var headername = entry[0];
   var val = entry [1];
   console.log(headername + "=" + val);
  }
  
  
 }
 
 var headerData = {};
 
  $(document).ready(function() {
     getHeaders();
     console.log("document is ready=" + headerData);
  });
 
 function getHeaders() {
  var req = new XMLHttpRequest();
  req.open('GET', document.location, false);
  req.send(null);
  var headers = req.getAllResponseHeaders().toLowerCase();
 
  var headersArray = {};
  
  var splitHeaders = headers.split(/\r\n|\n|\r/g);
  console.log("split headers:\n"  + splitHeaders);
  for (var i in splitHeaders){
   var currVal = splitHeaders[i];
   var entry = currVal.split(":");
   var headername = entry[0];
   var val = entry [1];
   console.log(headername + "=" + val);
   if(!(typeof headername === undefined) ) {
    if(headername.toUpperCase() === "SID" || headername.toUpperCase() === "MYCUSTOM" || headername.toUpperCase() === "SMUSER"  ){
          headersArray[headername]=val;
    }
      }
   
  }
  
  /* headersArray["firstName"]="arul" ;
  headersArray["lastName"]="kumaran" ; */
             
  console.log(headersArray);
  
  console.log(JSON.stringify(headersArray));
  headerData = headersArray;
  
 }
 
</script>

 
</body>
</html> 

Step 2: The server needs to have request filter that intercepts the "OPTIONS" request and add the header information required for the CORS to work. The headers to note are Access-Control-Allow-Origin and Access-Control-Allow-Headers.


package com.myapp.accounting.aes.poc.controller;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;


public class CorsFilter extends OncePerRequestFilter {

 @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) {
            // CORS "pre-flight" request
            response.addHeader("Access-Control-Allow-Origin", "*");
            response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
            response.addHeader("Access-Control-Allow-Headers", "origin, content-type, accept, x-requested-with, sid, mycustom, smuser");
            response.addHeader("Access-Control-Max-Age", "1800");//30 min
        }
        filterChain.doFilter(request, response);
    }
}

Step 3: Define the filter on web.xml file. The key is the last two declarations for the filter.

 
<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

 <!-- Processes application requests for Securities component related requests -->
 <servlet>
  <servlet-name>mvc-dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
  <servlet-name>mvc-dispatcher</servlet-name>
  <url-pattern>/simple/*</url-pattern>
 </servlet-mapping>

 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
 </context-param>

 <!-- Creates the Spring Container shared by all Servlets and Filters -->
 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>

 <filter>
  <filter-name>cors</filter-name>
  <filter-class>com.myapp.accounting.aes.poc.controller.CorsFilter</filter-class>
 </filter>

 <filter-mapping>
  <filter-name>cors</filter-name>
  <url-pattern>/simple/*</url-pattern>
 </filter-mapping>

</web-app>


The filter will intercept the cross domain OPTIONS request add the relevant headers to allow cross domain.


Step 4: The controller that is actually serving the subsequent RESTful GET, POST, PUT or DELETE requests need to send the relevant headers back. So, in your controller you need to populate the relevant headers as well.


 

package com.myapp.accounting.aes.poc.controller;

import java.util.Enumeration;

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

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/poc/main")
public class POCController {

 @RequestMapping(value = "/sum", method = RequestMethod.GET)
 public String getNonAjaxPage(HttpServletRequest request, HttpServletResponse response) {
  Enumeration headerNames = request.getHeaderNames();
  while(headerNames.hasMoreElements()){
   String nextElement = (String)headerNames.nextElement();
   System.out.println(nextElement + "=" + request.getHeaders(nextElement));
   response.addHeader(nextElement, request.getHeader(nextElement));
  }
  
  //adding som custom headers
  response.addHeader("SID", "I573558");
  response.addHeader("MYCUSTOM", "CUSTOM");
  
  return "poc-page/ajax-sum";
 }
 
 @RequestMapping(value = "/add", method = RequestMethod.GET)
 public ResponseEntity<String> add(
   @RequestParam(value = "inputNumber1", required = true) Integer inputNumber1,
   @RequestParam(value = "inputNumber2", required = true) Integer inputNumber2,
   Model model, HttpServletRequest request) {

  // Delegate to service to do the actual adding
  Integer sum = inputNumber1 + inputNumber2;

  String result = null;

  // @ResponseBody will automatically convert the returned value into JSON
  // format. you must have Jackson in your classpath

  
  
  result =  sum.toString();
  
  
  HttpHeaders responseHeaders = new HttpHeaders();
  
  Enumeration headerNames = request.getHeaderNames();
  while(headerNames.hasMoreElements()){
   String nextElement = (String)headerNames.nextElement();
   System.out.println(nextElement + "=" + request.getHeaders(nextElement));
   responseHeaders.set(nextElement, request.getHeader(nextElement));
  }
  
  
  //populating the header required for CORS
  responseHeaders.set("Access-Control-Allow-Origin", "*"); 
  
  
  return new ResponseEntity<String>(result, responseHeaders, HttpStatus.OK);
 }
}


That's all to it. The rest will remain same as the JSONP tutorial.


Labels: ,

Nov 21, 2012

JSONP and jQuery with Spring MVC, RESTful Web Service and Maven

Spring Interview Questions and Answers Q1 - Q14 are FAQs

Q1 - Q4 Overview & DIP Q5 - Q8 DI & IoC Q9 - Q10 Bean Scopes Q11 Packages Q12 Principle OCP Q14 AOP and interceptors
Q15 - Q16 Hibernate & Transaction Manager Q17 - Q20 Hibernate & JNDI Q21 - Q22 read properties Q23 - Q24 JMS & JNDI Q25 JDBC Q26 Spring MVC Q27 - Spring MVC Resolvers



If you have a GUI application (i.e. a war) and a separate RESTful service  application as a separate application running on two different domains, the you need JSONP  for your Ajax to make cross domain calls. In this demo, I will be building a single war and deploy it to two different domains. For example, local and dev. The initial  "sum" page will be loaded from the "local" domain, and once you click on the "add" button, the Ajax call will be made to the "dev" domain to get the calculated sum via the RESTful web service call via jsonp callback.







Here are the steps involved in setting up the relevant artifacts.



Step 1:  Create a new maven war project. This was explained in earlier tutorials.You could also create it within eclipse via File --> New --> Other --> Maven --> Maven Project. Skip the archetype selection by ticking the "create a simple project" check box. Fill in the "groupid" and "artifact id" details.The packaging is a "war".

Step 2: Wire up the relevant maven dependencies via the pom.xml.

<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.myapp.acoounting</groupId>
 <artifactId>aes-gui</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>war</packaging>
 <name>aes frontend</name>
 <description>GUI to expose aes services</description>

 <properties>
  <spring.version>3.1.0.RELEASE</spring.version>
  <servlet.jstl.version>1.2</servlet.jstl.version>
  <taglibs.standard.version>1.1.2</taglibs.standard.version>
  <servlet.api>2.5</servlet.api>
  <jackson-mapper-asl.version>1.9.8</jackson-mapper-asl.version>
 </properties>

 <dependencies>

  <!-- Spring -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-core</artifactId>
   <version>${spring.version}</version>
  </dependency>

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>${spring.version}</version>
  </dependency>

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-beans</artifactId>
   <version>${spring.version}</version>
  </dependency>
  
 
  <!-- Spring MVC framework -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>${spring.version}</version>
  </dependency>
  
  <!-- existence of Jackson and JAXB 2 on the classpath will automatically 
   create and register default JSON and XML converters -->
  <dependency>
   <groupId>org.codehaus.jackson</groupId>
   <artifactId>jackson-mapper-asl</artifactId>
   <version>${jackson-mapper-asl.version}</version>
   <scope>runtime</scope>
  </dependency>

  <!-- JSTL -->
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
   <version>${servlet.jstl.version}</version>
  </dependency>

  <dependency>
   <groupId>taglibs</groupId>
   <artifactId>standard</artifactId>
   <version>${taglibs.standard.version}</version>
  </dependency>

  <!-- for compile only, your container should have this -->
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>servlet-api</artifactId>
   <version>${servlet.api}</version>
   <scope>provided</scope>
  </dependency>

 </dependencies>
</project>



Step 3: Define the Spring MC controller class that will be used for both returning the initial "sum" web page as shown above and also exposes the RESTful  web service call to calculate the sum.

package com.myapp.acoounting.aes.poc.controller;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/poc/main")
public class POCController {

    //serves the initial page
 @RequestMapping(value = "/sum", method = RequestMethod.GET)
 public String getNonAjaxPage() {
  return "poc-page/ajax-sum"; // returns the poc-page/ajax-sum.jsp
 }  


 
 //serves the RESTful web service 
 @RequestMapping(value = "/add", method = RequestMethod.GET)
 public ResponseEntity<String> add(
   @RequestParam(value = "inputNumber1", required = true) Integer inputNumber1,
   @RequestParam(value = "inputNumber2", required = true) Integer inputNumber2,
   @RequestParam(value = "callback", required = false) String callback,
   Model model) {

  //do the actual adding
  Integer sum = inputNumber1 + inputNumber2;

  String result = null;

  // @ResponseBody will automatically convert the returned value into JSON
  // format. You must have Jackson in your classpath

  if (callback != null && callback.trim().length() > 0) {
   result = callback + "(" + sum + ")";
  }

  HttpHeaders responseHeaders = new HttpHeaders();
  
  return new ResponseEntity<String>(result, responseHeaders, HttpStatus.OK);
 }
} 


Step 4: The ajax-sum.jsp that gets returned when the following URL is typed on a browser.

http://localhost:8080/aes-gui/simple/poc/main/sum


You could notice here that the jQuery.js is loaded from the localhost domain and the Ajax call is made to the DEV domain.

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 
 <!-- This page and jQuery JS are downloaded via localhost domain--> 
 <script type="text/javascript" src="/aes-gui/resources/js/jquery/jquery-1.7.2.js"></script>
 
 <title>Spring MVC - JSONP and jQuery Tutorial</title>
</head>
<body>
 
<h3>Spring MVC - JSONP and jQuery Tutorial</h3>

<div style="border: 1px solid #ccc; width: 600px;">
 Add Two Numbers:
 
 <input id="inputNumber1" name="inputNumber1" type="text" size="5" > +
 <input id="inputNumber2" name="inputNumber2" type="text" size="5">
 <input type="submit" value="Add" onclick="add()" />
 
 Sum: <span id="sum"></span>
</div>
 
 
<script type="text/javascript">

 function add() {
  var url = 'http://DEV:8080/aes-gui/simple/poc/main/add?callback=?';

  console.log("logging...............");
  
  $.ajax({
   type : 'GET',
   url : url,
   data : {
    inputNumber1 : $("#inputNumber1").val(),
    inputNumber2 : $("#inputNumber2").val()
   },
   async : false,
   //contentType : "application/json",
   dataType : 'jsonp',
   //jsonp:  "callback",
   //jsonpCallback: processJSON(jsonData),
   success : function(response, textStatus, jqXHR) {
    console.log("reached here");
    // data contains the result
    // Assign result to the sum id
    $("#sum").replaceWith('<span id="sum">' + response + '</span>');
    console.log(response);

   },
   error : function(jqXHR, textStatus, errorThrown) {
    console.log(errorThrown);
   }
  });
 };
 
</script>

 
</body>
</html>

Make sure that you have downloaded the jquery-x.x.x.js library file and saved it under resources/js/jQuery/ folder.

Step 5: The next step is to wire up everything via web.xml and spring application context files.

Firstly, the web.xml file. This will be under the WEB-INF folder.

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

 <!-- Processes application requests for Securities component related requests -->
 <servlet>
  <servlet-name>mvc-dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
  
 <servlet-mapping>
  <servlet-name>mvc-dispatcher</servlet-name>
  <url-pattern>/simple/*</url-pattern>
 </servlet-mapping>
 
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
 </context-param>
 
 <!-- Creates the Spring Container shared by all Servlets and Filters -->
 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>

</web-app>



Secondly, the Spring application context file.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
            http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
 
   
    <context:annotation-config />
 <context:component-scan base-package="com.myapp.accounting.aes" />
 
 <mvc:annotation-driven />
 
 <bean
  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix">
   <value>/WEB-INF/pages/</value>
  </property>
  <property name="suffix">
   <value>.jsp</value>
  </property>
 </bean>
 
</beans>

That's all to it.






Labels: ,

Nov 20, 2012

Can you write a program in Java? for beginner to intermediate level coding




Q. Can you write code to generate a 4 digit number, where the same number cannot be repeted? For example, 1234 is valid, but 1124 or 1214 are invalid because the value 1 is repeated.
A. Firstly, write some pseudo code

1. Have an array or list of numbers - 0,1,2,3,4,5,6,7,8,9
2. Shuffle these numbers randomly in a list or array.
3. Pick the first 4 number from the list or array
4. concatenate these 4 randomly selected numbers and return them as a 4 digit result.

Now, you can write the code as shown below:


import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class GenerateRandomNumber2 {
 
 private static final int GENERATION_COUNT = 10; 
 private static final int NUMBER_OF_DIGITS = 4;

 public static void main(String[] args) {
  for (int i = 1; i <= GENERATION_COUNT; i++) {             //e.g. generate 10, 4-digit numbers 
   System.out.println(generateNumber(NUMBER_OF_DIGITS)); //e.g. generate a 4 digit number
  }
 }

 public static String generateNumber(int size) {
  //input validation -- i.e. precondition check
  if(size <= 0 || size > 10) {
   throw new IllegalArgumentException("Invalid size: " + size);
  }
  
  //power of varargs to create a list of 10 numbers from 0 to 9
  List<Integer> listOfNumbers = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
  
  //shuffle the numbers using the Collections utility class
  Collections.shuffle(listOfNumbers);
  
     StringBuilder sb = new StringBuilder(4);
     
     //get the size - for e.g first 4 shuffled numbers
     for (int i = 0; i<size; i++ ) {          //e.g. loops 4 times
   sb.append(listOfNumbers.get(i));     //string builder is used for mutation
  }
     
  return sb.toString();                   //convert to string object
 } 
}



Q. What if you didn't have the shuffle method in the Collections class?
A. Here is the pseudo code

1. Have an array  - 0,1,2,3,4,5,6,7,8,9
2. Loop through these numbers 4 times and randomly pick an index.
3. Once a number from an index is used, set the number on that index to say -1 so that the same number cannot be picked again.
4. If the same index was randomly picked, try again.
5. concatenate these 4 randomly selected numbers and return them as a 4 digit result.

public class GenerateRandomNumber {
 
 private static final int GENERATION_COUNT = 10; 
 private static final int NUMBER_OF_DIGITS = 4;

 public static void main(String[] args) {
  for (int i = 1; i <= GENERATION_COUNT; i++) {             //e.g. generate 10, 4-digit numbers 
   System.out.println(generateNumber(NUMBER_OF_DIGITS)); //e.g. generate a 4 digit number
  }
 }

 public static String generateNumber(int size) {
  // input validation -- i.e. precondition check
  if (size <= 0 || size > 10) {
   throw new IllegalArgumentException("Invalid size: " + size);
  }
  
  int[] myNumbers = new int[10];
  
  //store numbers 0 to 9 in an array
  for (int i = 0; i < myNumbers.length; i++) {
   myNumbers[i] = i;
  }
  
  StringBuilder sb = new StringBuilder(4);
  int index;
  
  for (int i = 0; i < size; i++) {
   index = (int) Math.floor(Math.random() * 10); // pick a random index
   if (myNumbers[index] >= 0) {
    sb.append(myNumbers[index]);
    myNumbers[index] = -1;  // mark it as used with -1
   } else {
    i--;   //try again if this index was already used i.e. value is -1
   }
  }
  return sb.toString();
 }
}


Some follow up questions:

Q. Was the above method thread-safe?
A. Yes, as it uses local variables and there is no shared data.

Q. How will you extend the above code so that each number generation happens on its own thread?
A. Spawn new threads as shown below


public static void main(String[] args) {
  for (int i = 1; i <= GENERATION_COUNT; i++) {    //e.g. generate 10, 4-digit numbers 
   Runnable r = new Runnable() {                   //e.g. generate a 4 digit number in its own thread 
    @Override
    public void run() {
     System.out.println(generateNumber(NUMBER_OF_DIGITS));
    }
   };
   
   Thread t= new Thread(r, "thread-" + i);
   t.start();  //executes the run( ) method
  }
 }

Q. How will you extend the code to retry when there is a duplicate 4 digit number?
A. Here is the added code to return only unique 4 digit numbers


    private static final int GENERATION_COUNT = 10; 
 private static final int NUMBER_OF_DIGITS = 4;
 
 private static final Set<String> setOfGeneratedNumbers = Collections.synchronizedSet(new HashSet<String>(10));

 public static void main(String[] args) {
  for (int i = 1; i <= GENERATION_COUNT; i++) {    //e.g. generate 10, 4-digit numbers 
   Runnable r = new Runnable() {                   //e.g. generate a 4 digit number in its own thread 
    @Override
    public void run() {
     System.out.println(getUniquelyGeneratedNumbers(NUMBER_OF_DIGITS));
    }
   };
   
   Thread t= new Thread(r, "thread-" + i);
   t.start();  //executes the run( ) method
  }
 }
 
 public static String getUniquelyGeneratedNumbers(int size) {
  // input validation -- i.e. precondition check
  if (size <= 0 || size > 10) {
   throw new IllegalArgumentException("Invalid size: " + size);
  }
  
  //generate a number first
  String genNumber = generateNumber(size);
  
  //check if the generated number was already generated, if yes -- generate another number
  while(setOfGeneratedNumbers.contains(genNumber)) {
   genNumber = generateNumber(size);
  }
  
  //store the generated number in the set
  synchronized(setOfGeneratedNumbers) {
     setOfGeneratedNumbers.add(genNumber);
  }
  
  return genNumber; 
 }
 


Q. Can you see any flaw(s) in the above code?
A. Mathematically you can only generate  5040 numbers. This was calculated as described below:

1. There are 10 numbers from 0 to 9.
2. You can't repeat the same number, so first digit can be chosen from 10 possible numbers, the second digit from 9 possible numbers, the third digit from 8 possible numbers, and the final and fourth digit from 7 possible numbers.
3. Hence, the number of 4 digit number combinations you can have is -- 10 * 9 * 8 * 7 = 540.

Flaw 1:

So, if the GENERATION_COUNT  is set to 5041, the while loop in the getUniquelyGeneratedNumbers(int size) method will never return false, and you will end up with an endless loop.


Flaw 2: 

The above code generate a new thread for each execution. This can potentially create 5040 new threads, which can be expensive new real production code and also can adversely impact performance as the CPU spends more time in context switching. It is best to use the Java 5 executor framework that lets create a pool of thread and reuse them.

Labels:

Nov 17, 2012

SOAP versus RESTful Web service -- comparison

Recently I attended an interview with a large investment bank, and I was quizzed on SOAP versus RESTful web service. The interview questions were targeted at ascertaining my understanding of the differences, pros and cons of each, and when to use what.

Web services are very popular and widely used to integrate similar (i.e. Java applications) and disparate systems (i.e. legacy applications and applications written in .Net etc). It is imperative to understand the differences, pros, and cons between each approach.

Key Area
SOAP based Web service
RESTful Web service
Specification/Platform Fundamentals (SF/PF)
Transport is platform & protocol neutral. Supports multiple protocols like HTTP(S), Messaging, TCP, UDP, SMTP, etc.

Permits only XML data format, hence language neutral.


You define operations, which tunnels through the POST or GET. The focus is on accessing the named operations and exposing the application logic as a service.



Defines the contract via WSDL.
Transport is protocol specific. Supports only HTTP or HTTPS protocols.


Permits multiple data formats like XML, JSON data, text, HTML, atom, RSS, etc.

Any browser can be used because the REST approach uses the standard GET, PUT, POST, and DELETE web operations. The focus is on accessing the named resources and exposing the data as a service.

Traditionally, the big drawback of REST was the lack of contract for the web service. This has changed with WSDL 2.0 defining non SOAP bindings and the emergence of WADL.


Simpler to implement. REST has Ajax support. It can use the XMLHttpRequest object.

Good for stateless CRUD (Create, Read, Update, and Delete) operations.

Performance Consideration (PC)
SOAP based reads cannot be cached. The application that uses SOAP needs to provide cacheing.
REST based reads can be cached. Performs and scales better.
Security (SE)
Supports both SSL security and WS-security, which adds some enterprise security features. Supports identity through intermediaries, not just point to point SSL.


WS-Security maintains its encryption right up to the point where the request is being processed.


WS-Security allows you to secure parts (e.g. only credit card details) of the message that needs to be secured. Given that encryption/decryption is not a cheap operation, this can be a performance boost for larger messages.

It is also possible with WS-Security to secure different parts of the message using different keys or encryption algorithms. This allows separate parts of the message to be read by different people without exposing other, unneeded information.

SSL security can only be used with HTTP. WS-Security can be used with other protocols like UDP, SMTP, etc.

Supports only point-to-point SSL security.

The basic mechanism behind SSL is that the client encrypts all of the requests based on a key retrieved from a third party. When the request is received at the destination, it is decrypted and presented to the service. This means the request is only encrypted while it is traveling between the client and the server. Once it hits the server (or a proxy which has a valid certificate), it is decrypted from that moment on.

The SSL encrypts the whole message, whether all of it is sensitive or not.

Transaction Management (TM)
Has comprehensive support for both ACID based transaction management for short-lived transactions and compensation based transaction management for long-running transactions. It also supports two-phase commit across distributed resources.
REST supports transactions, but it is neither ACID compliant nor can provide two phase commit across distributed transactional resources as it is limited by its HTTP protocol.
Quality of Service (QoS)
SOAP has success or retry logic built in and provides end-to-end reliability even through SOAP intermediaries.
REST does not have a standard messaging system, and expects clients invoking the service to deal with communication failures by retrying.
Best Practice (BP)
In general, a REST based web service is preferred due to its simplicity, performance, scalability, and support for multiple data formats. SOAP is favored where service requires comprehensive support for security, transactional reliability and stricter contract.


Q.  Differentiate between SOA (Service Oriented Architecture) versus WOA (Web Oriented Architecture)?
A. WOA extends SOA to be a light-weight architecture using technologies such as REST and POX (Plain Old XML). POX compliments REST. JSON is a variant for data returned by REST Web Services. It consumes less bandwidth and is easily handled by web developers mastering the Javascript language

SOA and WOA differ in terms of the layers of abstraction. SOA is a system-level architectural style that tries to expose business capabilities so that they can be consumed by many applications. WOA is an interface-level architectural style that focuses on the means by which these service capabilities are exposed to consumers. You can start out with a WOA and then grow into SOA.

Labels: ,

Nov 13, 2012

Coding in Java -- working with a List



Q. Can you write code to create sub lists for a given source list based on the supplied predicate?

For example, the source list might have numbers [1,2,3,4,5,6,7,8,9,10] and the sub list created might include

predicate: even numbers --> [2,4,6,8] (predicate means --> declare condition)
prdeicate: factor of 5  -->  [5, 10]


The code should be written such a way that it uses the best practices, easy to extend in the future, and maintainable.

A. This can be done a number of ways. The following code snippets take following key aspects into consideration.


1. Use generics.

2. Code to interface.

3. Make it extendable. New predicate implementation classes can be easily added in the future like OddNumberPrdeicate, PrimeNumberPredicate, etc and no change is required to the FilteredList class. It just takes a source list and a predicate implementation as arguments to create a sublist. If you need to build
heirachy of sublists, you can add a parent field in FilteredList class to maintain a hierachy.

4. Easy to test. The JUnit test classes are provided to test the FilteredList and the Predicate classes. Both happy path and exceptional paths are tested.

5. Composition is favored over inheritance hence the FilteredList was not defined as public class FilteredList extends List, instead the sublists was composed within FilteredList.

Here is the sample code.

Step 1: Define the Predicate interface

public interface Predicate<E> {
 boolean evaluate(E object);
}


Step 2: Define the implementation classes for the Predicate interface. One for filtering even numbers and another one for filtering factors of five.

public class EvenNumberPredicate implements Predicate<Integer> {

 @Override
 public boolean evaluate(Integer number) {
  return (number % 2 == 0) ? true : false;
 }

}


public class FactorOfFivePredicate implements Predicate<Integer> {

 @Override
 public boolean evaluate(Integer number) {
  return (number % 5 == 0) ? true : false;
 }

}


Step 3: Define the FilteredList interface and the implementation class.

import java.util.List;

public interface FilteredData<E> {
 abstract List<E> getSubData(List<E> sourceData);
}


import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class FilteredList<E> implements FilteredData<E> {
 
  private Predicate<E> predicate = null;
 
  public FilteredList(Predicate<E> predicate) {
  if (predicate == null) {
   throw new IllegalArgumentException("Expected to receive a non-null predicate on which to base the sublist!");
  }

  this.predicate = predicate;
 }

 public Predicate<E> getPredicate() {
  return predicate;
 }

 @Override
 public List<E> getSubData(List<E> sourceData) {
  List<E> subData = null;
  
  //return empty data
  if (sourceData == null || sourceData.isEmpty()) {
   subData = Collections.emptyList();
  }
        //return filtered data
  else {
   subData = new ArrayList<E>(sourceData.size());
   // Evaluate each item in the source to create the sublist...
   for (E item : sourceData) {
    if (predicate.evaluate(item)) {
     subData.add(item);
    }
   }
  }
  
  return subData;
 }
}




Step 4: Finally the JUnit class to test both positive and negative scenarios.

import java.util.Arrays;
import java.util.List;

import junit.framework.Assert;

import org.junit.Before;
import org.junit.Test;

public class FilteredListTest {
 
 private List<Integer> listOfIntegers;
 
 
 @Before
 public void init(){
  listOfIntegers = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
 }
 
 @Test
 public void testEvenNumbers(){
  Predicate<Integer> predicate = new EvenNumberPredicate();
  FilteredData<Integer> list = new FilteredList<Integer>(predicate);
  Assert.assertTrue(list.getSubData(listOfIntegers).size() == 5) ;
  Assert.assertEquals(Arrays.asList(2,4,6,8,10), list.getSubData(listOfIntegers));
 }
 
 @Test
 public void testFactorOfFiveNumbers(){
  Predicate<Integer> predicate = new FactorOfFivePredicate();
  FilteredData<Integer> list = new FilteredList<Integer>( predicate);
  Assert.assertTrue(list.getSubData(listOfIntegers).size() == 2) ;
  Assert.assertEquals(Arrays.asList(5,10), list.getSubData(listOfIntegers));
 }

 
 @Test(expected = IllegalArgumentException.class)
 public void testExceptionalCondition1(){
  new FilteredList<Integer>(null);
 }
 
 @Test
 public void testExceptionalCondition2(){
  Predicate<Integer> predicate = new FactorOfFivePredicate();
  FilteredData<Integer>  list = new FilteredList<Integer>(predicate);
  Assert.assertTrue(list.getSubData(null).size() == 0);
 }
 
}


Labels: ,

Nov 2, 2012

Overseas work opportunity, luck or skill?


The questions listed below appear frequently in the career forums.

Q. How can I work overseas?
Q. Can a Java/JEE software engineer go abroad to work within a short time?
Q. How can I find job in Singapore as a Java/JEE prfoessional with 2 year experience?

The question that prompted me to write this blog post was -- Overseas work opportunity, luck or skill? The simple answer is that you need both. The key is that you need to "prepare" to go abroad.

"The meeting of preparation with opportunity generates the offsping we call luck" -- by Tony Robbins

"I feel that luck is preparation meeting opportunity" -- by Oprah Winfrey


In preparing, you need to ask the question that why an overseas employer should pick you and not someone else who has similar or better skill sets? Here are a few things that I believe that will improve your chances.

You need to have good communication and interpersonal skills. Your technical skills must be complemented with good soft skills in general and great communication and interpersonal skills are a must. If you feel you lack these skills, better start working on them as it takes time to develop them.

Your career is your responsibility and you need to develop the much needed job hunting skills including effective resume writing skills, good interview preparation, building an online presence and networking skills. These skills can make you standout from other professionals who are same as or more qualified than you are.


Expand your horizons by trying different avenues to be discovered with an online presence. See this blog post to see what you can do as a software engineer to open more doors. Your talents and accomplishments need to be effectively  promoted to build additional income streams.

Good domain knowledge does matter. Especially in finance and telecom. Banking and Finance is a big domain and the employers prefer candidates with prior experience.

If you are already working with an outsourcing company, opportunity to go onsite is very much dependent on being on the right project at the right time. Some are indeed lucky to get such a project sooner than later, but for most, it takes some calculated risks or moves within or outside the company. Sometimes you will have to make your own luck. Try to get good grasp of the 16 key areas that will help you solve problems and standout from your competition to be picked.

There are so many developers out there with Java/JEE skills. Having Java/JEE + a rare skill sets like SOA tools, BPM tools, SAP integration, etc can improve your chances to be picked. Research the overseas job postings and try to acquire relevant skills.

Constantly do your research via industry specific forums and Googling for keywords like  "IT skills shortage", "IT skills shortage list", "IT skills demand list", "working visas USA", "working visas UK", "Overseas IT job placements", etc.

Knowing a foreign language other than English can be advantageous. With the world becoming seemingly smaller with cheaper airfares, globalization, and the world wide web, it makes sense that most businesses seek out employees who can communicate in more than one language. If you are bi or multi-lingual, your skill set will make you more marketable in a multitude of industries, from the corporate world to government agencies, and from hospitality/tourism to real estate. Spanish, German, French, Japanese, and Russian are popular languages and do your research as to a specific country you intend to travel to. But learning a new language from scratch is a daunting task and might not worth the effort. The time can be spent more productively in other avenues listed above.

Finally, you need a workable plan and stick to it with lots of patience and perseverance  The plan should include
  • Why do you want to work overseas? better income, better savings, working holiday, gain new insights, better opportunities to open more doors in terms of starting your own business or selling your ideas, etc. 
  • What countries are you interested in working in, and why? some countries may require you to learn a foreign language.
  • Why should an overseas employer or outsourcing company pick you? How can you effectively sell your accomplishments, skills, and experience? Do you have any rare skill sets? Do you have any specific domain knowledge? 
  • The grass is not always greener on the other side. So, do your research about the cost of living, expected earnings after tax, life style, adjusting to cultural differences, etc.  

Hope this blog post will help you make your own luck by self assessing your current skill sets and acquiring what is required to increase your chances. 

Labels: