Showing posts with label Spring 3.2 RESTful web services JSON Maven curl. Show all posts
Showing posts with label Spring 3.2 RESTful web services JSON Maven curl. Show all posts

Sunday, June 16, 2013

Spring 4 & REST

In this blog I will show you how to use Spring to implement RESTful web services. As with any other Spring MVC application, you will need a controller class that is used to expose your web service methods.

Here is an example:
package com.noushin.spring.ws.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.noushin.spring.ws.model.Student;
import com.noushin.spring.ws.service.StudentService;

/**
 * 
 * This class is used to demonstrate how Spring is used to implement RESTful web services.
 * 
 * @author nbashir
 *
 */
@Controller
@RequestMapping("/student")
public class StudentController {

   @Autowired
   StudentService studentService;
   
   /**
    * To test: curl -i -v -X POST -H "Content-Type: application/json;charset=UTF-8" http://localhost:8080/ws/student/add -d '{"name":"John Wait", "grade":1}'
    *  
    * @param student JSON representation of student to be added
    * 
    */
   @RequestMapping(value="/add", method=RequestMethod.POST, consumes="application/json;charset=UTF-8", produces="application/json;charset=UTF-8")
   @ResponseBody
   public Student addStudent(@RequestBody Student student) {
      return studentService.addStudent(student);
   }
   
   /**
    * To test:  curl -i -v -X GET -H "Content-Type: application/json;charset=UTF-8" http://localhost:8080/ws/student/get/16a61b8e-f6ae-459f-bb92-164bd7b72d07
    * 
    * @param id Student ID
    * @return A student in JSON format
    */
   @RequestMapping(value="/get/{id}", method=RequestMethod.GET, produces="application/json;charset=UTF-8")
   @ResponseBody
   public Student getStudent(@PathVariable String id) {
      return studentService.getStudent(id);
   }
   
   /**
    * To test:  curl -i -v -X GET -H "Content-Type: application/json;charset=UTF-8" http://localhost:8080/ws/student/getall
    * 
    * @return A list of all students in JSON format
    */
   @RequestMapping(value="/getall", method=RequestMethod.GET, produces="application/json;charset=UTF-8")
   public @ResponseBody List<Student> getStudents() {
      return studentService.getStudents();
   }
   
   /**
    * To test: curl -i -v -X GET http://localhost:8080/ws/student/testexception
    * Only for demonstration purposes.
    * 
    * @throws Exception 
    */
   @RequestMapping(value="/testexception", method=RequestMethod.GET)
   public void testException() throws Exception {
      System.out.println("Service is available and throws exception.");
      studentService.throwException();
   }
   
   /**
    * Exception Handler for this service.
    * 
    * @param exception Use a different subclass of Exception, if you need a more refined exception handling.
    * @return The same student object populated with info on the exception. 
    */
   @ExceptionHandler(Exception.class)
   public @ResponseBody String handleMyException(Exception exception) {
      return (new String("Error : " + exception.getMessage()));
   }
}
We will need a service class to support our controller methods. Note how service class is accessed in the controller by Spring Autowired feature.
package com.noushin.spring.ws.service;

import java.util.List;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.noushin.spring.ws.dao.StudentDao;
import com.noushin.spring.ws.model.Student;

/**
 * 
 * This class is used to demonstrate how Spring is used to implement RESTful web services.
 * 
 * @author nbashir
 * 
 */
@Component
public class StudentService {

   @Autowired
   StudentDao studentDao;

   public Student addStudent(Student student) {
      student.setId(UUID.randomUUID().toString());
      studentDao.addStudent(student);
      return student;
   }

   public Student getStudent(String id) {
      return studentDao.getStudent(id);
   }

   public List<Student> getStudents() {
      return studentDao.getStudents();
   }

   public void updateStudent(Student student) {
      studentDao.updateStudent(student);
   }

   public void deleteStudent(String id) {
      studentDao.deleteStudent(id);
   }

   public void throwException()  throws Exception {
      throw new Exception("This is an artificial Exception to test WS exception handling.");
   }
}
In a typical application, you would need a data layer that manages your data. Here is a typical Data Access Object (DAO) class. Note in this example, I am just using a local static Map to hold the data. In real world applications, you would be accessing some database or similar technology.
package com.noushin.spring.ws.dao;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.springframework.stereotype.Component;

import com.noushin.spring.ws.model.Student;

/**
 * 
 * This class is used to demonstrate how Spring is used to implement RESTful web services.
 * 
 * @author nbashir
 * 
 */
@Component
public class StudentDao {

   private static Map<String, Student> students = new HashMap<String, Student>();

   public void addStudent(Student student) {
      students.put(student.getId(), student);
   }

   public Student getStudent(String id) {
      return students.get(id);
   }

   public List<Student> getStudents() {
      return new ArrayList<Student>(students.values());
   }

   public void updateStudent(Student student) {
      deleteStudent(student.getId());
      addStudent(student);
   }

   public void deleteStudent(String id) {
      students.remove(id);
   }
}
And finally the business object you are trying to manage:
package com.noushin.spring.ws.model;

/**
 * 
 * This class is used to demonstrate how Spring is used to implement RESTful web services.
 * 
 * @author nbashir
 * 
 */
public class Student {

   private String id;
   private int grade;
   private String name;

   /**
    * Need this constructor for JSON mapping
    */
   public Student() {
      super();
   }

   public Student(int grade, String name) {
      super();
      this.grade = grade;
      this.name = name;
   }

   public Student(String id, int grade, String name) {
      super();
      this.id = id;
      this.grade = grade;
      this.name = name;
   }

   public String getId() {
      return id;
   }

   public void setId(String id) {
      this.id = id;
   }

   public int getGrade() {
      return grade;
   }

   public void setGrade(int grade) {
      this.grade = grade;
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }
}

To run your application as a web based application, you'd need a web.xml and servlet configuration file. Add the following files to your application WEB-INF directory. Example:

~/workspace/ws/src/main/webapp/WEB-INF/web.xml
~/workspace/ws/src/main/webapp/WEB-INF/ws-servlet.xml
web.xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app version="3.0" metadata-complete="true"
    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_3_0.xsd">

    <display-name>Spring WS Demo</display-name>
    <description>Spring WS Demo</description>

    <servlet>
        <servlet-name>ws</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>ws</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/ws-servlet.xml</param-value>
    </context-param>
 
     <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>/WEB-INF/classes/log4j.properties</param-value>
    </context-param>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
   
</web-app>
ws-servlet.xml:
<?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:p="http://www.springframework.org/schema/p"
    xmlns:jms="http://www.springframework.org/schema/jms"
    xmlns:amq="http://activemq.apache.org/schema/core"
    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.2.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
                        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">


    <context:component-scan base-package="com.noushin.spring.ws" />
    <mvc:annotation-driven />
       
</beans>
Since I am using Maven to manage my application build and dependecies, here is the pom.xml you need:
<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.noushin.spring</groupId>
    <artifactId>ws</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>ws</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <junit.version>4.10</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <spring.version>4.1.0.RELEASE</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        
        <!-- Logging -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        
        <!-- Spring Test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>
        
        <!-- Spring web -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>

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

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

        <!-- Jackson mapper is used by spring to convert Java POJOs to JSON strings. -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.2.3</version>
        </dependency>
        
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.1.1</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.1</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${endorsed.dir}</outputDirectory>
                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>javax</groupId>
                                    <artifactId>javaee-endorsed-api</artifactId>
                                    <version>6.0</version>
                                    <type>jar</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

And since no application development is ever complete without proper unit testing, here are some basic method level testing of StudentService. Note, I already included the curl commands you need to test your web service methods from command line in Linux in StudentController class.

StudentServiceTest.java:
package com.noushin.spring.ws.service;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.UUID;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.noushin.spring.ws.model.Student;
import com.noushin.spring.ws.service.StudentService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class StudentServiceTest {

   @Autowired
   StudentService service;

   @Before
   public void setUp() throws Exception {
   }

   @After
   public void tearDown() throws Exception {
   }

   @Test
   public void testAddStudent() {
      Student student = service.addStudent(new Student(5, "John Wait"));
      Student addedStudent = service.getStudent(student.getId());
      assertTrue(addedStudent.getGrade() == student.getGrade());
   }

   @Test
   public void testDeleteStudent() {
      Student student = service.addStudent(new Student(5, "John Wait"));
      student = service.getStudent(student.getId());
      service.deleteStudent(student.getId());
      Student deletedStudent = service.getStudent(student.getId());
      assertTrue(deletedStudent == null);
   }

   @Test
   public void testGetStudents() {
      Student student = new Student(1, "Jane Porter");
      service.addStudent(student);
      
      student = new Student(2, "John Downy");
      service.addStudent(student);

      student = new Student(3, "Kate Summerfield");
      service.addStudent(student);

      ArrayList<Student> students = (ArrayList<Student>) service.getStudents();
      assertTrue(students != null);      
      Iterator<Student> it = (Iterator<Student>) students.iterator();
      while (it.hasNext()) {
         student = (Student) it.next();
         System.out.println(student.getGrade() + ":" + student.getName());
      }
   }

   @Test
   public void testUpdateStudent() {
      Student student = service.addStudent(new Student(1, "Jane Porter"));
      int grade1 = student.getGrade();
      System.out.println("Student >>> " + student.getId() + " - Name : " + student.getName() + " - Grade : " + student.getGrade());
      student.setGrade(2);
      Student updatedStudent = service.getStudent(student.getId());
      System.out.println("updatedStudent >>> " + updatedStudent.getId() + " - Name : " + updatedStudent.getName() + " - Grade : " + updatedStudent.getGrade());
      int grade2 = updatedStudent.getGrade();
      assertTrue(grade1 != grade2);
   }
}

Since I am using Spring Test framework, here is the context file you need to run your tests. Place the following file in
~workspace/ws/src/test/resources/com/noushin/spring/ws/service/StudentServiceTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:amq="http://activemq.apache.org/schema/core" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                        http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.noushin.spring.ws" />
    <context:annotation-config />

</beans>


One more note regarding debugging Spring web services. Add the following lines to your log4j.properties file. They will come handy when you get errors when invoking web service methods:
log4j.category.org.springframework.web.servlet.DispatcherServlet=DEBUG
log4j.category.org.springframework.web.servlet.mvc=TRACE