Unit 5 M
Unit 5 M
Unit-5
Spring REST
Spring REST- An Introduction
Spring's web module carries the support for REST as well.
The Dispatcher Servlet interacts with handler mappings to determine which controller to execute upon
user request. Same way, it consults view resolver to decide the view in which the response to be rendered.
1. An application can have multiple controllers. So, the DispatcherServlet consults the handler
mapping to decide the controller that should work on the request.
2. The handler mapping uses request URL to choose the controller. Once done, this decision should
be sent to the DispatcherServlet back.
3. After receiving appropriate controller name, DispatcherServlet sends the request to the controller.
4. The controller receives the request from the DispatcherServlet and executes the business logic that
is available in the service layer.
5. Once done with the business logic, controller generates some information that needs to be
sent back to the client and displayed in the web browser. This information is called as model. Now,
the controller sends the model and logical name of the view to the DispatcherServlet.
6. The DispatcherServlet passes the logical View name to the ViewResolver, which determines the
actual view.
7. The actual view is sent back to the DispatcherServlet, now.
8. The DispatcherServlet then passes the model to the View, which generates the response.
9. The generated respose is sent back to the DispatcherServlet.
10. The DispatcherServlet returns the generated response over to the client.
Note: By default, DispatcherServlet supports GET, HEAD, POST, PUT, PATCH and DELETE HTTP
methods only.
Spring REST
Spring provides support for creating RESTful web services using Spring MVC. Availing this support
requires, Spring version 3.0 and above.
The REST controllers are different from MVC controllers because REST controllers' methods return
results which can be mapped to a representation rather than a view.
For this, Spring 3.0 introduced @ResponseBody annotation. So, the methods of REST controllers that are
annotated with @ResponseBody tells the DispatcherServlet that the result of execution need not to be
mapped with a view.
@RestController
In Spring 4.0, the @RestController annotation was introduced.
This annotation is a combination of @Controller and @ResponseBody.
This annotation when used on a REST controller class bounds all the values returned by controller
methods to the response body.
Developing Spring REST as a Boot project still simplifies the developers' job as a lot of things are auto-
configured.
In our course, we will learn, how to develop a Spring REST application using Spring Boot.
The below table summarizes the differences between Spring MVC and Spring REST.
Spring MVC Spring REST
Spring MVC response is a View/Page by In Spring REST data is returned directly back to the
default client
Additionally, Spring Boot provides embedded defaults (example - deploys a web application in the
embedded Tomcat server) and opinions (example - suggests Hibernate implementation of JPA for ORM).
In this course, we will look at a telecom application called Infytel. As the course proceeds, we will build
this app incrementally to make it a collection of REST endpoints. And, we will use Spring Boot to
develop this application.
Infytel - An Overview
The Infytel application will have the following REST resources.
Controller Functionality
CustomerController Add, update, delete and fetch customers
PlanController Fetch plan details
CallDetailsController Fetch call details
And, these resources contact the same database.
About the course section has the complete set of downloadable code and script for the Infytel case study.
Step 1: Extract the project and import the same as Maven project in STS.
Step 4: Pay attention to the console to gain information on port number and the context path.
Step 5: Gain request mapping details of the RESTful service methods. That is, have a look at the request
and method mapping annotations of the controllers to understand the
URI format
template parameters, if any
request method (GET, POST, etc.,)
MIME type of Java objects to be passed
Spring REST requests are delegated to the DispatcherServlet that identifies the specific controller with the
help of handler mapper. Then, the identified controller processes the request and renders the response.
This response, in turn, reaches the dispatcher servlet and finally gets rendered to the client.
What are the steps involved in exposing business functionality as a RESTful web service?
Step 2: Add the service methods that are mapped against the standard HTTP methods
Since we are going to develop REST applications using Spring Boot, lot of configurations needed in Step-
3 can be avoided as Spring Boot takes care of the same.
Any class that needs to be exposed as a RESTful resource has to be annotated with @RestController
@RestController
This annotation is used to create REST controllers.
It is applied on a class in order to mark it as a request handler/REST resource.
This annotation is a combination of @Controller and @ResponseBody annotations.
@ResponseBody is responsible for the automatic conversion of the response to a JSON string
literal. If @Restcontroller is in place, there is no need to use @ResponseBody annotation to denote
that the Service method simply returns data, not a view.
@RestController is an annotation that takes care of instantiating the bean and marking the same as
REST controller.
It belongs to the package, org.springframework.web.bind.annotation.RestController
@RequestMapping
This annotation is used for mapping web requests onto methods that are available in the resource
classes. It is capable of getting applied at both class and method levels. At method level, we use
this annotation mostly to specify the HTTP method.
1. import org.springframework.web.bind.annotation.RestController;
2. import org.springframework.web.bind.annotation.RequestMapping;
3. @RestController
4. @RequestMapping("/customers")
5. public class CustomerController
6. {
7. @RequestMapping(method=RequestMethod.POST)
8. public String createCustomer()
9. {
10. //Functionality goes here
11. }
12. }
REST resources have handler methods with appropriate HTTP method mappings to handle the incoming
HTTP requests. And, this method mapping usually happens with annotations.
For example, in the Infytel application, developed for a telecom company, we would like to create a REST
resource that can deal with operations like creating, deleting, fetching and updating the customers. Here
comes the summary of HTTP operations and the corresponding mappings. An important point to be noted
here is, Infytel is a Spring Boot application.
New Annotation
that can be applied
HTTP CustomerController Method Annotation to be applied
URI instead of
Method method description at the method level
@RequestMapping
at the method level
Will fetch
all the
@RequestMapping(method
customers
= RequestMethod.GET)
/customers GET fetchCustomer() of Infytel @GetMapping
App and
return the
same.
@RequestMapping(method
Will create
= RequestMethod.POST)
/customers POST createCustomer() a new @PostMapping
customer
Following is the code of a REST controller that has several handler methods with appropriate method
mapping annotations.
1. @RestController
2. @RequestMapping("/customers")
3. public class CustomerController
4. {
5. //Fetching the customer details
6. @GetMapping
7. public String fetchCustomer()
8. {
9. //This method will fetch the customers of Infytel and return the same.
10. return "customers fetched successfully";
11. }
12. //Adding a new customer
13. @PostMapping
14. public String createCustomer()
15. {
16. //This method will persist the details of a customer
17. return "Customer added successfully";
18. }
19. //Updating an existing customer
20. @PutMapping
21. public String updateCustomer()
22. {
23. //This method will update the details of an existing customer
24. return "customer details updated successfully";
25. }
26. //Deleting a customer
27. @DeleteMapping
28. public String deleteCustomer()
29. {
30. //This method will delete a customer
31. return "customer details deleted successfully";
32. }
33. }
Since we are creating a Spring Boot application with spring-boot-starter-web dependency, we need
not pay much focus on the configurations.
spring-boot-starter-web dependency in the pom.xml will provide the dependencies that are
required to build a Spring MVC application including the support for REST. Also, it will ensure
that our application is deployed on an embedded Tomcat server and we can replace this option
with the one that we prefer based on the requirement.
1. <dependency>
2. <groupId>org.springframework.boot</groupId>
3. <artifactId>spring-boot-starter</artifactId>
4. </dependency>
5. <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
6. <dependency>
7. <groupId>org.springframework.boot</groupId>
8. <artifactId>spring-boot-starter-web</artifactId>
9. </dependency>
Application class is simply annotated with @SpringBootApplication
1. @SpringBootApplication
Demo:
Objectives:
To create a Spring REST application using Spring Boot.
Steps:
Step 1: Create a Maven project using Spring Initializer with maven web dependencies and import the
same in STS.
After importing the project, if the dependencies are not getting downloaded automatically, update Maven
project as shown below.
Step 2: Modify the imported project according to the following project structure:
Step 3: Look at the class, InfytelDemo1Application in com.infytel package, this will be auto-created
when the project is generated. We will modify this only when we need to include custom configurations.
1. package com.infytel;
2. import org.springframework.boot.SpringApplication;
3. import org.springframework.boot.autoconfigure.SpringBootApplication;
4. @SpringBootApplication
5. public class InfytelDemo1Application {
6. public static void main(String[] args) {
7. SpringApplication.run(InfytelDemo1Application.class, args);
8. }
9. }
40. }
Step 6: Make sure that the project's pom.xml looks similar to the one that is shown below.
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
5. <modelVersion>4.0.0</modelVersion>
6. <parent>
7. <groupId>org.springframework.boot</groupId>
8. <artifactId>spring-boot-starter-parent</artifactId>
9. <version>2.1.4.RELEASE</version>
10. <relativePath /> <!-- lookup parent from repository -->
11. </parent>
12. <groupId>com.infytel</groupId>
13. <artifactId>infytel_demo1</artifactId>
14. <version>0.0.1-SNAPSHOT</version>
15. <name>infytel_demo1</name>
16. <description>Spring REST using Spring Boot - Basic Annotations</description>
17. <properties>
18. <java.version>1.8</java.version>
19. </properties>
20. <dependencies>
21. <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
22. <dependency>
23. <groupId>org.springframework.boot</groupId>
24. <artifactId>spring-boot-starter-web</artifactId>
25. </dependency>
26. <dependency>
27. <groupId>org.springframework.boot</groupId>
28. <artifactId>spring-boot-starter-test</artifactId>
29. <scope>test</scope>
30. </dependency>
31. </dependencies>
32. <build>
33. <plugins>
34. <plugin>
35. <groupId>org.springframework.boot</groupId>
36. <artifactId>spring-boot-maven-plugin</artifactId>
37. </plugin>
38. </plugins>
39. </build>
40. </project>
Step 7: Deploy the service on the server by executing the class containing the main method.
So, we have successfully created and deployed RESTful web service. Now let us see how the same can be
tested using Postman client.
From the drop-down, select GET and enter http://localhost:8080/infytel-1/customers into the URL field
to test the service. Click on Send. You will get the following response:
@RequestBody
@RequestBody is the annotation that helps map our HTTP request body to a Java DTO. And, this
annotation has to be applied on the local parameter (Java DTO) of the request method.
Whenever Spring encounters @RequestBody, it takes the help of the registered HttpMessageConverters
which will help convert the HTTP request body to Java Object depending on the MIME type of the
Request body.
Example: In the below code snippet, the incoming HTTP request body is deserialized to CustomerDTO. If
the MIME type of the incoming data is not mentioned, it will be considered as JSON by default and
Spring will use the JSON message converter to deserialize the incoming data.
1. @PostMapping
2. public String createCustomer( @RequestBody CustomerDTO customerDTO)
3. {
4. // logic goes here
5. }
We can specify the expected Mime type using the consumes attribute of the HTTP method matching
annotation
Example: The above code is equivalent to the below code that promotes the application of the attribute,
consumes.
1. @PostMapping(consumes="application/json")
2. public ResponseEntity<String> createCustomer( @RequestBody CustomerDTO customerDTO)
3. {
4. // logic goes here
5. }
consumes attribute can be supplied with a single value or an array of media types as shown below
1. consumes = "text/plain"
2. or
3. consumes = {"text/plain", "application/json"}
4. Some more valid values:
5. consumes = "application/json"
6. consumes = {"application/xml", "application/json"}
ResponseEntity
Scenario-1
Java objects can be returned by the handler method just like how the normal Java methods can return an
object.
Example: In the below example, the fetchCustomer() returns a list of Customer Objects. This list will be
converted by Spring's message converter to JSON data.
1. @GetMapping
2. public List<CustomerDTO> fetchCustomer()
3. {
4. //business logic goes here
5. return customerService.fetchCustomer();
6. }
Scenario-2
We can specify the MIME type, to which the data to be serialized, using the produces attribute of HTTP
method matching annotations
1. @GetMapping(produces="application/json")
2. public List<CustomerDTO> fetchCustomer()
3. {
4. //This method will return the customers of Infytel
5. return customerService.fetchCustomer();
6. }
Just like consumes, the attribute, produces can also take a single value or an array of MIME types
1. Valid values for produces attribute:
2. produces = "text/plain"
3. produces = {"text/plain", "application/json"}
4. produces = {"application/xml", "application/json"}
While sending a response, we may like to set the HTTP status code and headers .To help achieving this,
we can use ResponseEntity class.
ResponseEntity<T> Will help us add a HttpStatus status code and headers to our response.
Example: In the below code snippet, createCustomer() method is returning a String value and setting the
status code as 200.
1. @PostMapping(consumes="application/json")
2. public ResponseEntity<String> createCustomer(@RequestBody CustomerDTO customerDTO)
3. {
VLITS, Vadlamudi. Page 15
API & Micro Services Unit-5 – Spring REST
As shown in the code snippet, we can use the static functions of ResponseEntity class that are available for
standard Http codes (ok() method is used, here). One more way of setting response entity is as follows.
1. ResponseEntity(T body, MultiValueMap<String,String> headers, HttpStatus status)
Example:
1. @PostMapping(consumes="application/json")
2. public ResponseEntity<String> createCustomer(@RequestBody CustomerDTO customerDTO)
3. {
4. HttpHeaders responseHeaders = new HttpHeaders();
5. responseHeaders.set("MyResponseHeaders", "Value1");
6. String response = customerService.createCustomer(customerDTO);
7. return new ResponseEntity<String>(response, responseHeaders, HttpStatus.CREATED);
8. }
ResponseEntity.badRequest().body(message).build();
ResponseBuilder Returns a ResponseBuilder with the status, NOT_FOUND.
ResponseEntity
Description
method
notFound()
ResponseEntity.notFound().build();
Objectives:
To create a Spring REST application using Spring Boot, where the handler methods of a REST controller,
consumes and produces Java objects. We will learn,
1. The usage of produces and consumes attributes of HTTP method handling annotations.
2. The usage of ResponseEntity
Scenario: An online telecom app called Infytel is exposing its customer management profile as a
RESTful service. The Customer resource titled CustomerController allows us to create, fetch, delete
and update customer details. This demo works with the following HTTP operations.
MethodName URI HTTP Method Remarks
createCustomer() /customers POST Uses consumes attribute,@RequestBody
fetchCustomer() /customers GET Uses produces attribute
Steps:
Step 1: Create a Maven project using Spring Initializer with web dependency and import the same in STS.
Step 2: Modify the imported project according to the following project structure:
Step 3: Look at the class InfytelDemo2Application in com.infytel package that gets created automatically
when the project is generated. We will modify this only when we need to go with custom configuration.
1. package com.infytel;
2. import org.springframework.boot.SpringApplication;
3. import org.springframework.boot.autoconfigure.SpringBootApplication;
4. @SpringBootApplication
5. public class InfytelDemo2Application {
6. public static void main(String[] args) {
7. SpringApplication.run(InfytelDemo2Application.class, args);
8. }
9. }
Step 5: Create the DTO classes CustomerDTO, FriendFamilyDTO and PlanDTO under
com.infytel.dto package:
1. package com.infytel.dto;
2. import java.util.List;
3. public class CustomerDTO {
4. long phoneNo;
5. String name;
6. String email;
7. int age;
8. char gender;
9. List<FriendFamilyDTO> friendAndFamily;
10. String password;
11. String address;
12. PlanDTO currentPlan;
13. public String getEmail() {
14. return email;
15. }
16. public void setEmail(String email) {
17. this.email = email;
18. }
19. public PlanDTO getCurrentPlan() {
20. return currentPlan;
21. }
22. public void setCurrentPlan(PlanDTO currentPlan) {
23. this.currentPlan = currentPlan;
24. }
25. public String getPassword() {
26. return password;
27. }
28. public void setPassword(String password) {
29. this.password = password;
30. }
31. public String getAddress() {
32. return address;
33. }
34. public void setAddress(String address) {
35. this.address = address;
36. }
37. public List<FriendFamilyDTO> getFriendAndFamily() {
38. return friendAndFamily;
39. }
40. public void setFriendAndFamily(List<FriendFamilyDTO> friendAndFamily) {
41. this.friendAndFamily = friendAndFamily;
42. }
43. public long getPhoneNo() {
44. return phoneNo;
45. }
46. public void setPhoneNo(long phoneNo) {
47. this.phoneNo = phoneNo;
48. }
49. public String getName() {
50. return name;
51. }
52. public void setName(String name) {
53. this.name = name;
54. }
55. public int getAge() {
56. return age;
57. }
58. public void setAge(int age) {
59. this.age = age;
60. }
61. public char getGender() {
62. return gender;
63. }
64. public void setGender(char gender) {
65. this.gender = gender;
66. }
67. @Override
68. public String toString() {
69. return "CustomerDTO [phoneNo=" + phoneNo + ", name=" + name + ", age=" + age + ", gender="
+ gender + ", friendAndFamily=" + friendAndFamily + ", password=" + password + ", address=" +
address + "]";
70. }
71. }
72.
1. package com.infytel.dto;
2. public class FriendFamilyDTO {
3. long phoneNo;
4. long friendAndFamily;
5. public long getPhoneNo() {
6. return phoneNo;
7. }
8. public void setPhoneNo(long phoneNo) {
9. this.phoneNo = phoneNo;
10. }
11. public long getFriendAndFamily() {
12. return friendAndFamily;
13. }
14. public void setFriendAndFamily(long friendAndFamily) {
15. this.friendAndFamily = friendAndFamily;
16. }
17. public FriendFamilyDTO(long phoneNo, long friendAndFamily) {
18. this();
19. this.phoneNo = phoneNo;
20. this.friendAndFamily = friendAndFamily;
21. }
22. public FriendFamilyDTO() {
23. super();
24. }
25. @Override
26. public String toString() {
27. return "FriendFamilyDTO [phoneNo=" + phoneNo + ", friendAndFamily=" + friendAndFamily +
"]";
28. }
29. }
30.
1. package com.infytel.dto;
2. import javax.xml.bind.annotation.XmlRootElement;
3. import java.util.List;
4. import javax.annotation.PostConstruct;
5. import org.springframework.stereotype.Repository;
6. import com.infytel.dto.CustomerDTO;
7. import com.infytel.dto.FriendFamilyDTO;
8. import com.infytel.dto.PlanDTO;
9. @Repository
10. public class CustomerRepository {
11. List<CustomerDTO> customers = null;
12. //Equivalent/similar to constructor. Here, populates the DTOs in a hard-coded way
13. @PostConstruct
14. public void initializer()
15. {
16. CustomerDTO customerDTO = new CustomerDTO();
17. PlanDTO planDTO = new PlanDTO();
18. planDTO.setPlanId(1);
19. planDTO.setPlanName("Simple");
20. planDTO.setLocalRate(3);
21. planDTO.setNationalRate(5);
22. customerDTO.setAddress("Chennai");
23. customerDTO.setAge(18);
24. customerDTO.setCurrentPlan(planDTO);
25. customerDTO.setGender('m');
26. customerDTO.setName("Jack");
27. customerDTO.setEmail("[email protected]");
28. customerDTO.setPassword("ABC@123");
29. customerDTO.setPhoneNo(9951212222l);
30. List<FriendFamilyDTO> friendAndFamily = new ArrayList<>();
31. friendAndFamily.add(new FriendFamilyDTO(customerDTO.getPhoneNo(),800000145));
32. friendAndFamily.add(new FriendFamilyDTO(customerDTO.getPhoneNo(),700000145));
33. customerDTO.setFriendAndFamily(friendAndFamily);
34. customers = new ArrayList<>();
35. customers.add(customerDTO);
36. }
37. //adds the received customer object to customers list
38. public void createCustomer(CustomerDTO customerDTO)
39. {
40. customers.add(customerDTO);
41. }
42. //returns a list of customers
43. public List<CustomerDTO> fetchCustomer()
44. {
Step 9: Make sure that the project's pom.xml looks similar to the one that is shown below.
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
5. <modelVersion>4.0.0</modelVersion>
6. <parent>
7. <groupId>org.springframework.boot</groupId>
8. <artifactId>spring-boot-starter-parent</artifactId>
9. <version>2.1.4.RELEASE</version>
10. <relativePath /> <!-- lookup parent from repository -->
11. </parent>
12. <groupId>com.infosys</groupId>
13. <artifactId>infytel_demo2</artifactId>
14. <version>0.0.1-SNAPSHOT</version>
15. <name>infytel_demo2</name>
16. <description>Understanding the differences between Spring MVC and Spring
REST</description>
17. <properties>
18. <java.version>1.8</java.version>
19. </properties>
20. <dependencies>
21. <dependency>
22. <groupId>org.springframework.boot</groupId>
23. <artifactId>spring-boot-starter-web</artifactId>
24. </dependency>
25. <dependency>
26. <groupId>org.springframework.boot</groupId>
27. <artifactId>spring-boot-starter-test</artifactId>
28. <scope>test</scope>
29. </dependency>
30. </dependencies>
31. <build>
32. <plugins>
33. <plugin>
34. <groupId>org.springframework.boot</groupId>
35. <artifactId>spring-boot-maven-plugin</artifactId>
36. </plugin>
37. </plugins>
38. </build>
39. </project>
Step 10: Deploy the appliaction by executing the class that contains the main method.
So, we have successfully created and deployed the REST endpoints. Now, let us see how to test the same
using Postman client.
Output Screenshots
Testing Web Service using Postman
From the drop-down select GET and enter http://localhost:8080/infytel-2/customers into the URL field
to test the service. Click on Send. You will get the following response:
From the drop-down select POST and enter http://localhost:8080/infytel-2/customers into the URL field
to test the service. And click on body to enter following JSON data. The below data is used to create a
new customer in infytel.
The JSON data has to match the DTO object to which it is converted.
1. {"phoneNo":8866886876,"name":"Smith","email":"[email protected]","age":28,
2. "gender":"m"}
Note: Observe the CustomerDTO's variable names. It is important that the variable name in
CustomerDTO and JSON key has to match exactly, for the correct mapping to happen.
Now, from the drop-down, select GET and enter the URL http://localhost:8080/infytel-2/customers into
the URL field to test the service. Click on Send. Following will be the output of the request.
Enter the URL: http://localhost:8080/infytel-2/customers into the address bar of the browser to get the
result of GET request.
doesn't need a separate channel to get transferred. So, how to receive this kind of data that appears in the
URI.
Before we look into the ways to extract the URI data, we will have a look at the formal categorization of
the data that appear in the request URI.
1. Query Parameter
2. Path Variables
3. Matrix Variables
Query Parameter:
Query parameters or request parameters usually travel with the URI and are delimited by question
mark.
The query parameters in turn are delimited by ampersand from one another.
The annotation @RequestParam helps map query/request parameters to the method arguments.
@RequestParam annotation expects the name that it holds to be similar to the one that is present
in the request URI.This makes the code tightly coupled with the request URI.
Example: Assume, there is a RestController called CallDetailsController that has a service method to
return the call details of a specific phone number on a particular date. The service method here, takes the
phoneNo of the customer and the date as query /request parameters.
Example URI:
1. URI:http://<<hostname>>:<<port>>/<<contextpath>>/calldetails?calledBy=9123456789&calledO
n=12/5/2019
1. @RestController
2. @RequestMapping("/calldetails")
3. public class CallDetailsController
4. {
5. //Fetching call details based on the request parameters being passed along with the URI
6. @GetMapping(produces = "application/json")
7. public List<CallDetailsDTO> callDetails(
8. @RequestParam("calledBy") long calledBy, @RequestParam("calledOn") String calledOn)
9. {
10. //code goes here
11. }
12. }
Path Variables
Path variables are usually available at the end of the request URIs delimited by slash (/).
@Pathvariable annotation is applied on the argument of the controller method whose value needs to
be extracted out of the request URI.
A request URI can have any number of path variables.
Multiple path variables require the usage of multiple @PathVariable annotations.
@Pathvariable can be used with any type of request method. For example, GET, POST, DELETE,
etc.,
We have to make sure that the name of the local parameter (int id) and the placeholder ({id}) are same.
Name of the PathVariable annotation's argument (@PathVarible("id")) and the placeholder ({id}) should
be equal, otherwise.
1. @GetMapping("/{id}")
2. public String controllerMethod(@PathVariable int id){}
OR
1. @GetMapping("/{id}")
2. public String controllerMethod(@PathVariable("id") int empId){}
Example: Assume, there is a REST controller called CustomerController that has service methods to
update and delete the customers. These operations are simply based out of the phone number of the
customer that is passed as part of the request URI.
Below are the sample URIs that contain data as part of them.
1. URI: PUT:http://<<hostname>>:<<port>>/<<contextpath>>/customers/9123456789
2. DELETE:http://<<hostname>>:<<port>>/<<contextpath>>/customers/9123456789
The handler methods with the provision to extract the URI parameters are presented below.
1. @RestController
2. @RequestMapping("/customers")
3. public class CustomerController
4. {
5. //Updating an existing customer
6. @PutMapping(value = "/{phoneNumber}", consumes = "application/json")
7. public String updateCustomer(
8. @PathVariable("phoneNumber") long phoneNumber,
9. @RequestBody CustomerDTO customerDTO) {
10. //code goes here
11. }
12. // Deleting a customer
13. @DeleteMapping(value="/{phoneNumber}",produces="text/html")
14. public String deleteCustomer(
Matrix variables:
Matrix variables are a block/segment of values that travel along with the URI. For
example, /localRate=1,2,3/
These variables may appear in the middle of the path unlike query parameters which appear only
towards the end of the URI.
Matrix variables follow name=value format and use semicolon to get delimited from one other
matrix variable.
A matrix variable can carry any number of values, delimited by commas.
@MatrixVariable is used to extract the matrix variables.
Example: Assume, there is a REST controller called PlanController that has a service to return the details
of Plans based on the search criteria, localRates.
Below is the URI path. Observe that a variable localRate with two values separated by "," appear in the
middle of the path.
1. URI:http://<<hostname>>:<<port>>/<<contextpath>>/plans/localRate=1,4 /plan
See how the matrix variable, localRate gets extracted in the controller method.
Code:
1. @RestController
2. @RequestMapping("/plans")
3. public class PlanController
4. {
5. //{query} here is a place holder for the matrix variables that travel in the URI,
6. //it is not mandatory that the client URI should hold a string literal called query
7. @GetMapping(value = "/{query}/plan", produces = {"application/json","application/xml"})
8. public EntityList<PlanDTO> plansLocalRate(
9. @MatrixVariable(pathVar="query") Map<String, List<Integer>> map ) {
10. //code goes here
11. }
12. }
Note:If the matrix variable appears towards the end of the URI as in the below example,
1. URI:http://localhost:8081/infytel-1/customers/calldetails/phoneNo=9123456789
Objectives:
To create a Spring REST application using Spring Boot where some of the REST methods take path
variables as arguments. In this demo, we will learn the usage of @PathVariable.
Scenario: An online telecom app called Infytel is exposing its “customer management profile” as
RESTful service. The Customer resource allows us to create, fetch, delete and update customer details.
This demo concentrates on the following HTTP operations.
HTTP
MethodName URI Remarks
Method
To update Customer details of a customer
updateCustomer /customers/{phoneNumber} PUT identified via his phoneNumber passed as path
variable
To delete an existing Customer of Infytel
deleteCustomer /customers/{phoneNumber} DELETE identified via his phoneNumber passed as path
variable
Steps:
Step 1: Create a Maven project using Spring Initializer with web dependency and import the same in STS.
Step 2: Modify the imported project according to the following project structure:
Step 3: Look at the class InfytelDemo3Application in com.infytel package that gets created
automatically when the project is generated. We will modify this only when we need to go with custom
configuration.
1. package com.infytel;
2. import org.springframework.boot.SpringApplication;
3. import org.springframework.boot.autoconfigure.SpringBootApplication;
4. @SpringBootApplication
5. public class InfytelDemo3Application {
6. public static void main(String[] args) {
7. SpringApplication.run(InfytelDemo3Application.class, args);
8. }
9. }
16. @RequestMapping("/customers")
17. public class CustomerController
18. {
19. //CustomerController needs to contact CustomerService, hence dependency injecting the
customerService reference
20. @Autowired
21. private CustomerService customerService;
22. //Fetching customer details
23. @GetMapping(produces="application/json")
24. public List<CustomerDTO> fetchCustomer()
25. {
26. return customerService.fetchCustomer();
27. }
28. //Adding a customer
29. @PostMapping(consumes="application/json")
30. public ResponseEntity<String> createCustomer( @RequestBody CustomerDTO customerDTO)
31. {
32. String response = customerService.createCustomer(customerDTO);
33. return ResponseEntity.ok(response);
34. }
35. //Updating an existing customer
36. @PutMapping(value = "/{phoneNumber}", consumes = "application/json")
37. public String updateCustomer(@PathVariable("phoneNumber") long phoneNumber,
@RequestBody CustomerDTO customerDTO)
38. {
39. return customerService.updateCustomer(phoneNumber, customerDTO);
40. }
41. //Deleting a customer
42. @DeleteMapping("/{phoneNumber}")
43. public String deleteCustomer(@PathVariable("phoneNumber") long phoneNumber)
44. {
45. return customerService.deleteCustomer(phoneNumber);
46. }
47. }
6. String name;
7. String email;
8. int age;
9. char gender;
10. List<FriendFamilyDTO> friendAndFamily;
11. String password;
12. String address;
13. PlanDTO currentPlan;
14. public String getEmail() {
15. return email;
16. }
17. public void setEmail(String email) {
18. this.email = email;
19. }
20. public PlanDTO getCurrentPlan() {
21. return currentPlan;
22. }
23. public void setCurrentPlan(PlanDTO currentPlan) {
24. this.currentPlan = currentPlan;
25. }
26. public String getPassword() {
27. return password;
28. }
29. public void setPassword(String password) {
30. this.password = password;
31. }
32. public String getAddress() {
33. return address;
34. }
35. public void setAddress(String address) {
36. this.address = address;
37. }
38. public List<FriendFamilyDTO> getFriendAndFamily() {
39. return friendAndFamily;
40. }
41. public void setFriendAndFamily(List<FriendFamilyDTO> friendAndFamily) {
42. this.friendAndFamily = friendAndFamily;
43. }
44. public long getPhoneNo() {
45. return phoneNo;
46. }
47. public void setPhoneNo(long phoneNo) {
26. }
27. public Integer getLocalRate() {
28. return localRate;
29. }
30. public void setLocalRate(Integer localRate) {
31. this.localRate = localRate;
32. }
33. public PlanDTO() {
34. super();
35. }
36. @Override
37. public String toString() {
38. return "PlanDTO [planId=" + planId + ", planName=" + planName + ", nationalRate=" +
nationalRate+ ", localRate=" + localRate + "]";
39. }
40. }
25. customerDTO.setGender('m');
26. customerDTO.setName("Jack");
27. customerDTO.setEmail("[email protected]");
28. customerDTO.setPassword("ABC@123");
29. customerDTO.setPhoneNo(9951212222l);
30. List<FriendFamilyDTO> friendAndFamily = new ArrayList<>();
31. friendAndFamily.add(new FriendFamilyDTO(customerDTO.getPhoneNo(),800000145));
32. friendAndFamily.add(new FriendFamilyDTO(customerDTO.getPhoneNo(),700000145));
33. customerDTO.setFriendAndFamily(friendAndFamily);
34. customers = new ArrayList<>();
35. customers.add(customerDTO);
36. }
37. //adds the received customer object to customers list
38. public void createCustomer(CustomerDTO customerDTO)
39. {
40. customers.add(customerDTO);
41. }
42. //returns a list of customers
43. public List<CustomerDTO> fetchCustomer()
44. {
45. return customers;
46. }
47. //deletes the passed customer from the list
48. public void deleteCustomer(CustomerDTO customer)
49. {
50. customers.remove(customer);
51. }
52. }
13. @Autowired
14. private CustomerRepository customerRepository;
15. //makes a call to repository method for adding the customer
16. public String createCustomer(CustomerDTO customerDTO)
17. {
18. customerRepository.createCustomer(customerDTO);
19. return "Customer with "+customerDTO.getPhoneNo()+" added successfully";
20. }
21. //makes a call to repository method for returning a list of customers
22. public List<CustomerDTO> fetchCustomer()
23. {
24. return customerRepository.fetchCustomer();
25. }
26. /* makes a call to repository method for fetching the customers list and
27. updates the customer's details
28. */
29. public String updateCustomer(long phoneNumber, CustomerDTO customerDTO)
30. {
31. String response = "Customer of: "+phoneNumber+" does not exist";
32. for(CustomerDTO customer : customerRepository.fetchCustomer())
33. {
34. if(customer.getPhoneNo() == phoneNumber)
35. {
36. if(customerDTO.getName()!=null)
37. customer.setName(customerDTO.getName());
38. if(customerDTO.getAddress()!=null)
39. customer.setAddress(customerDTO.getAddress());
40. if(customerDTO.getPassword()!=null)
41. customer.setPassword(customerDTO.getPassword());
42. response = "Customer of phoneNumber "+customer.getPhoneNo()+" got updated successfully";
43. break;
44. }
45. }
46. return response;
47. }
48. /* makes a call to repository method for fetching the customers list and
49. then calls the repository's deleteCustomer() method with the customer to be deleted
50. */
51. public String deleteCustomer(long phoneNumber)
52. {
53. String response = "Customer of: "+phoneNumber+" does not exist";
54. for (CustomerDTO customer : customerRepository.fetchCustomer()) {
Step 9: Make sure that the project's pom.xml looks similar to the one shown below.
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
5. <modelVersion>4.0.0</modelVersion>
6. <parent>
7. <groupId>org.springframework.boot</groupId>
8. <artifactId>spring-boot-starter-parent</artifactId>
9. <version>2.1.4.RELEASE</version>
10. <relativePath /> <!-- lookup parent from repository -->
11. </parent>
12. <groupId>infytel</groupId>
13. <artifactId>infytel_demo3</artifactId>
14. <version>0.0.1-SNAPSHOT</version>
15. <name>infytel_demo3</name>
16. <description>Demo project for Spring Boot</description>
17. <properties>
18. <java.version>1.8</java.version>
19. </properties>
20. <dependencies>
21. <dependency>
22. <groupId>org.springframework.boot</groupId>
23. <artifactId>spring-boot-starter-web</artifactId>
24. </dependency>
25. <dependency>
26. <groupId>org.springframework.boot</groupId>
27. <artifactId>spring-boot-starter-test</artifactId>
28. <scope>test</scope>
29. </dependency>
30. </dependencies>
31. <build>
32. <plugins>
33. <plugin>
34. <groupId>org.springframework.boot</groupId>
35. <artifactId>spring-boot-maven-plugin</artifactId>
36. </plugin>
37. </plugins>
38. </build>
39. </project>
Step 10: Deploy the application on the server by executing the class containing the main method.
So, we have successfully created and deployed REST endpoints. Now, let us see how to test the same
using Postman client.
Output
Testing the REST endpoints using Postman
Step 1: Launch Postman.
Step 2: Test GET request
From the dropdown, select GET and enter http://localhost:8080/infytel-3/customers into the URL field
to test the service. Click on Send. The following response will be generated
From the drop-down, select PUT and enter http://localhost:8080/infytel-3/customers/ 9951212222 into
the URL field to test the service. And, click on the body to enter the following JSON data. The below data
is used to update the details of an existing customer (customer of phone number, 9951212222)
The JSON data has to match the DTO object to which it is converted.
1. {
2. "name":"Jack Smith",
3. "address":"Hyderabad,TG,India",
4. "password": "infy@123"
5. }
Note: Observe the CustomerDTO's variable names. It is important that the variable name in
CustomerDTO and JSON key has to match exactly, for the correct mapping to happen.
Now, from the drop-down, select GET and enter the URL http://localhost:8080/infytel-3/customers into
the URL field to test the service. Click on Send. Following is the response that will be generated
Now, from the drop down, select GET and enter the URL http://localhost:8080/infytel-3/customers into
the URL field to test the service. Click on Send. Following will be the response
Notice that the details of the customer of phone number, 9951212222 is not appearing while fetching the
data after the delete operation.
CookPick online grocery application has a requirement where the users should be able to search for a
product based on its name. As a result of the search, a list of product details from different vendors should
be rendered.
Note:
1. You can implement this requirement in the project that you used for previous exercise.
2. List of products needs to be rendered in JSON format.
3. You can simply hardcode the product details. It is optional to use a database to retrieve the product
details.
*Time given for this exercise indicates the time required to create the REST endpoint and is excluding the
time required to write the logic for retrieving the product details from database.
@RequestParam Video
To create a Spring REST application using Spring Boot where the REST endpoint works with request
parameters. Here, we will learn the
Usage of @RequestParam
Scenario:An online telecom app called Infytel is exposing its customer management profile as a RESTful
service. The application has a customer resource titled CustomerController allows us to create, fetch,
delete and update customer details. This application has two more controllers, CallDetailsController and
PlanController to deal with call and plan details respectively. This demo has the following HTTP
operations.
HTTP
Controller Class Method Name URI Remarks
Method
This fetches the
calls made by a
customer on a
CallDetailsController fetchCallDetails() /calldetails?calledBy=&calledOn= GET certain date.Uses
@RequestParam to
read query string
values passed
createCustomer()
To create a new
CustomerController /customers POST
customer
HTTP
Controller Class Method Name URI Remarks
Method
existing customers
To update a
CustomerController updateCustomer() /customers/{phoneNumber} PUT
customer details
To delete an
CustomerController deleteCustomer /customers/{phoneNumber} DELETE
existing customer
Step 1: Create a Maven project using Spring Initializer with web dependency and import the same in STS.
Step 2: Modify the imported project according to the following project structure:
Step 3: Look at the class InfytelDemo4Application in com.infytel package that gets created automatically
when the project is generated. We will modify this only when we need to go with custom configuration
1. package com.infytel;
2. import org.springframework.boot.SpringApplication;
3. import org.springframework.boot.autoconfigure.SpringBootApplication;
4. @SpringBootApplication
5. public class InfytelDemo4Application {
6. public static void main(String[] args) {
7. SpringApplication.run(InfytelDemo4Application.class, args);
8. }
9. }
5. import org.springframework.beans.factory.annotation.Autowired;
6. import org.springframework.web.bind.annotation.GetMapping;
7. import org.springframework.web.bind.annotation.RequestMapping;
8. import org.springframework.web.bind.annotation.RequestParam;
9. import org.springframework.web.bind.annotation.RestController;
10. import com.infytel.dto.CallDetailsDTO;
11. import com.infytel.service.CallDetailsService;
12. @RestController
13. @RequestMapping("/calldetails")
14. public class CallDetailsController
15. {
16. @Autowired
17. private CallDetailsService callDetailsService;
18. //Fetching call details based on the request parameters being passed along with the URI
19. //Make sure giving the current date (calledOn) on which the demo gets executed
20. //CallDetailsRepository has code to populate calledOn with the current date
21. @GetMapping(produces = "application/json")
22. public List<CallDetailsDTO> fetchCallDetails(@RequestParam("calledBy") long calledBy,
@RequestParam("calledOn") String calledOn)
23. {
24. return callDetailsService.fetchCallDetails(calledBy, LocalDate.parse(calledOn,
DateTimeFormatter.ofPattern("MM-dd-yyyy")));
25. }
26. }
27.
1. package com.infytel.controller;
2. import java.util.List;
3. import org.springframework.beans.factory.annotation.Autowired;
4. import org.springframework.http.ResponseEntity;
5. import org.springframework.web.bind.annotation.DeleteMapping;
6. import org.springframework.web.bind.annotation.GetMapping;
7. import org.springframework.web.bind.annotation.PathVariable;
8. import org.springframework.web.bind.annotation.PostMapping;
9. import org.springframework.web.bind.annotation.PutMapping;
10. import org.springframework.web.bind.annotation.RequestBody;
11. import org.springframework.web.bind.annotation.RequestMapping;
12. import org.springframework.web.bind.annotation.RestController;
13. import com.infytel.dto.CustomerDTO;
14. import com.infytel.dto.PlanDTO;
15. import com.infytel.service.CustomerService;
16. @RestController
17. @RequestMapping("/customers")
6. LocalDate calledOn;
7. int duration;
8. public long getCalledBy() {
9. return calledBy;
10. }
11. public void setCalledBy(long calledBy) {
12. this.calledBy = calledBy;
13. }
14. public long getCalledTo() {
15. return calledTo;
16. }
17. public void setCalledTo(long calledTo) {
18. this.calledTo = calledTo;
19. }
20. public LocalDate getCalledOn() {
21. return calledOn;
22. }
23. public void setCalledOn(LocalDate calledOn) {
24. this.calledOn = calledOn;
25. }
26. public int getDuration() {
27. return duration;
28. }
29. public void setDuration(int duration) {
30. this.duration = duration;
31. }
32. @Override
33. public String toString() {
34. return "CallDetailsDTO [calledBy=" + calledBy + ", calledTo=" + calledTo + ", calledOn=" +
calledOn + ", duration=" + duration + "]";
35. }
36. }
1. package com.infytel.dto;
2. import java.util.List;
3. public class CustomerDTO {
4. long phoneNo;
5. String name;
6. String email;
7. public String getEmail() {
8. return email;
9. }
1. package com.infytel.dto;
2. public class ErrorMessage {
3. private int errorCode;
4. private String message;
5. public int getErrorCode() {
6. return errorCode;
7. }
8. public void setErrorCode(int errorCode) {
9. this.errorCode = errorCode;
10. }
11. public String getMessage() {
12. return message;
13. }
14. public void setMessage(String message) {
15. this.message = message;
16. }
17. }
1. package com.infytel.dto;
2. public class FriendFamilyDTO {
3. long phoneNo;
4. long friendAndFamily;
5. public long getPhoneNo() {
6. return phoneNo;
7. }
8. public void setPhoneNo(long phoneNo) {
9. this.phoneNo = phoneNo;
10. }
11. public long getFriendAndFamily() {
12. return friendAndFamily;
13. }
14. public void setFriendAndFamily(long friendAndFamily) {
15. this.friendAndFamily = friendAndFamily;
16. }
17. public FriendFamilyDTO(long phoneNo, long friendAndFamily) {
18. this();
19. this.phoneNo = phoneNo;
20. this.friendAndFamily = friendAndFamily;
21. }
22. public FriendFamilyDTO() {
23. super();
24. }
25. @Override
26. public String toString() {
27. return "FriendFamilyDTO [phoneNo=" + phoneNo + ", friendAndFamily=" + friendAndFamily +
"]";
28. }
29. }
1. package com.infytel.dto;
2. import javax.xml.bind.annotation.XmlRootElement;
3. @XmlRootElement
4. public class PlanDTO {
5. Integer planId;
6. String planName;
7. Integer nationalRate;
8. Integer localRate;
9. public Integer getPlanId() {
10. return planId;
11. }
1. package com.infytel.repository;
2. import java.util.ArrayList;
3. import java.util.List;
4. import javax.annotation.PostConstruct;
5. import org.springframework.stereotype.Component;
6. import com.infytel.dto.CustomerDTO;
7. import com.infytel.dto.FriendFamilyDTO;
8. import com.infytel.dto.PlanDTO;
9. @Component
1. package com.infytel.service;
2. import java.util.List;
3. import org.springframework.beans.factory.annotation.Autowired;
4. import com.infytel.dto.CustomerDTO;
5. import com.infytel.repository.CustomerRepository;
6. public class CustomerService {
7. @Autowired
8. private CustomerRepository customerRepository;
9. // calls repository layer method to create customer
10. public String createCustomer(CustomerDTO customerDTO) {
11. return customerRepository.createCustomer(customerDTO);
12. }
13. // calls repository layer method to fetch customers
14. public List<CustomerDTO> fetchCustomer() {
15. return customerRepository.fetchCustomer();
16. }
17. // calls repository layer method to delete customer
18. public String deleteCustomer(long phoneNumber) {
19. return customerRepository.deleteCustomer(phoneNumber);
20. }
21. // calls repository layer method to update customer
22. public String updateCustomer(long phoneNumber, CustomerDTO customerDTO) {
23. return customerRepository.updateCustomer(phoneNumber, customerDTO);
24. }
25. }
Step 9: Make sure that the project's pom.xml looks similar to the one that is shown below.
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
5. <modelVersion>4.0.0</modelVersion>
6. <parent>
7. <groupId>org.springframework.boot</groupId>
8. <artifactId>spring-boot-starter-parent</artifactId>
9. <version>2.1.4.RELEASE</version>
10. <relativePath /> <!-- lookup parent from repository -->
11. </parent>
12. <groupId>infytel</groupId>
13. <artifactId>infytel_demo4</artifactId>
14. <version>0.0.1-SNAPSHOT</version>
15. <name>infytel_demo4</name>
16. <description>Demo project for Spring Boot</description>
17. <properties>
18. <java.version>1.8</java.version>
19. </properties>
20. <dependencies>
21. <dependency>
22. <groupId>org.springframework.boot</groupId>
23. <artifactId>spring-boot-starter-web</artifactId>
24. </dependency>
25. <dependency>
26. <groupId>org.springframework.boot</groupId>
27. <artifactId>spring-boot-starter-test</artifactId>
28. <scope>test</scope>
29. </dependency>
30. </dependencies>
31. <build>
32. <plugins>
33. <plugin>
34. <groupId>org.springframework.boot</groupId>
35. <artifactId>spring-boot-maven-plugin</artifactId>
36. </plugin>
37. </plugins>
38. </build>
39. </project>
Step 10: Deploy the application on the server by executing the class containing the main method.
So, we have successfully created and deployed the REST endpoints. Now, let us see how can we test the
same using Postman client.
Output Screenshots
Notice how, the query string has been passed and the same has been fetched in the RestController.
CookPick online grocery application has a requirement where the users should be able to search for a
product based on its name and vendor. As a result of the search, a list of product details matching with the
given name and vendor should be rendered.
HTTP Business
RESTful URL
Action Operation
/product?productName=<product_name>&productVendor=<vendor_name> GET getProducts ()
This method,
Takes product name and vendor as input
Renders a list of products that match the condition
Note:
1. You can implement this requirement in the project that you used for previous exercise.
2. List of products needs to be rendered in JSON format.
3. You can simply hardcode the product details. It is optional to use a database to retrieve the product
details.
*Time given for this exercise indicates the time required to create the REST endpoint and is excluding the
time required to write the logic for retrieving the product details from database.
Objectives: To create a Spring REST application that has code to work with matrix variables that are sent
as part of URI. Here, we will learn
How to configure a Spring Boot web application to provide the support for matrix variables
The usage of @MatrixVariable
Scenario: An online telecom app called Infytel exposes its functionalities as RESTful resources. And, one
among such resources is PlanController that deals with the plans that are available with Infytel.
Controller HTTP
Method Name URI Remarks
Class Method
PlanController fetchPlans() /plans GET fetches all the plans
fetches only those plans that matches
PlanController plansLocalRate() /plans/{query}/plan GET the localrates provided via matrix
parameter
Steps:
Step 1: Create a Maven project using Spring Initializer with Web dependency and import the same
in STS.
Step 2: Modify the imported project according to the following project structure:
Step 3: Look at the class InfytelDemo5Application in com.infytel package that gets created automatically
when the project is generated. We will modify this only when we need to go with custom configuration.
To enable matrix variables, configurePathMatch() method of WebMvcConfigurer needs to overriden.
Matrix variables are disabled by default and the following configuration
urlPathHelper.setRemoveSemicolonContent(false);
11. }
12. // To support matrix parameters
13. @Override
14. public void configurePathMatch(PathMatchConfigurer configurer) {
15. UrlPathHelper urlPathHelper = new UrlPathHelper();
16. urlPathHelper.setRemoveSemicolonContent(false);
17. configurer.setUrlPathHelper(urlPathHelper);
18. }
19. }
1. package com.infytel.controller;
2. import java.util.List;
3. import org.springframework.beans.factory.annotation.Autowired;
4. import org.springframework.http.ResponseEntity;
5. import org.springframework.web.bind.annotation.DeleteMapping;
6. import org.springframework.web.bind.annotation.GetMapping;
7. import org.springframework.web.bind.annotation.PathVariable;
8. import org.springframework.web.bind.annotation.PostMapping;
9. import org.springframework.web.bind.annotation.PutMapping;
10. import org.springframework.web.bind.annotation.RequestBody;
11. import org.springframework.web.bind.annotation.RequestMapping;
12. import org.springframework.web.bind.annotation.RestController;
13. import com.infytel.dto.CustomerDTO;
14. import com.infytel.dto.PlanDTO;
15. import com.infytel.service.CustomerService;
16. @RestController
17. @RequestMapping("/customers")
18. public class CustomerController {
19. List<CustomerDTO> customers = null;
20. List<Long> friendFamily = null;
21. PlanDTO plan = null;
22. @Autowired
23. private CustomerService customerService;
24. // fetch customer details
25. @GetMapping(produces = "application/json")
26. public List<CustomerDTO> fetchCustomer() {
27. return customerService.fetchCustomer();
28. }
29. // add customer
30. @PostMapping(consumes = "application/json")
31. public ResponseEntity<String> createCustomer(@RequestBody CustomerDTO customerDTO) {
32. String response = "";
33. response = customerService.createCustomer(customerDTO);
34. return ResponseEntity.ok(response);
35. }
36. // update an existing customer
37. @PutMapping(value = "/{phoneNumber}", consumes = "application/json")
38. public String updateCustomer(@PathVariable("phoneNumber") long phoneNumber,
@RequestBody CustomerDTO customerDTO) {
39. return customerService.updateCustomer(phoneNumber, customerDTO);
40. }
41. // delete customer
42. @DeleteMapping("/{phoneNumber}")
43. public String deleteCustomer(@PathVariable("phoneNumber") long phoneNumber) {
44. return customerService.deleteCustomer(phoneNumber);
45. }
46. }
1. package com.infytel.controller;
2. import java.util.ArrayList;
3. import java.util.List;
4. import java.util.Map;
5. import java.util.Set;
6. import org.springframework.beans.factory.annotation.Autowired;
7. import org.springframework.web.bind.annotation.GetMapping;
8. import org.springframework.web.bind.annotation.MatrixVariable;
9. import org.springframework.web.bind.annotation.RequestMapping;
10. import org.springframework.web.bind.annotation.RestController;
11. import com.infytel.dto.EntityList;
12. import com.infytel.dto.PlanDTO;
13. import com.infytel.service.PlanService;
14. @RestController
15. @RequestMapping("/plans")
16. public class PlanController {
17. private EntityList<PlanDTO> plans;
18. @Autowired
19. private PlanService planService;
20. // Get all available plans
21. @GetMapping(produces = { "application/xml" })
22. public EntityList<PlanDTO> fetchPlans() {
23. plans = new EntityList<>(planService.fetchPlans());
24. return plans;
25. }
26. @GetMapping(value = "/{query}/plan", produces = { "application/xml", "application/json" })
27. public EntityList<PlanDTO> plansLocalRate(@MatrixVariable(pathVar = "query") Map<String,
List<Integer>> map) {
28. Set<String> keysLocalRates = map.keySet();
29. List localRates = new ArrayList();
30. for (String key : keysLocalRates) {
31. for (int i = 0; i < map.get(key).size(); i++) {
32. localRates.add(map.get(key).get(i));
33. }
34. }
35. plans = new EntityList<>(planService.plansLocalRate(localRates));
36. return plans;
37. }
38. }
1. package com.infytel.dto;
2. import java.util.List;
45. }
46. public void setPhoneNo(long phoneNo) {
47. this.phoneNo = phoneNo;
48. }
49. public String getName() {
50. return name;
51. }
52. public void setName(String name) {
53. this.name = name;
54. }
55. public int getAge() {
56. return age;
57. }
58. public void setAge(int age) {
59. this.age = age;
60. }
61. public char getGender() {
62. return gender;
63. }
64. public void setGender(char gender) {
65. this.gender = gender;
66. }
67. @Override
68. public String toString() {
69. return "CustomerDTO [phoneNo=" + phoneNo + ", name=" + name + ", age=" + age + ", gender="
+ gender + ", friendAndFamily=" + friendAndFamily + ", password=" + password + ", address=" +
address + "]";
70. }
71. }
1. package com.infytel.dto;
2. import java.util.ArrayList;
3. import java.util.List;
4. import javax.xml.bind.annotation.XmlAnyElement;
5. import javax.xml.bind.annotation.XmlRootElement;
6. import javax.xml.bind.annotation.XmlSeeAlso;
7. @XmlRootElement
8. @XmlSeeAlso({ PlanDTO.class })
9. public class EntityList<T> {
10. private List<T> listOfEntityObjects;
11. public EntityList() {
12. listOfEntityObjects = new ArrayList<>();
13. }
14. public EntityList(List<T> listOfEntityObjects) {
15. this.listOfEntityObjects = listOfEntityObjects;
16. }
17. @XmlAnyElement
18. public List<T> getItems() {
19. return listOfEntityObjects;
20. }
21. }
1. package com.infytel.dto;
2. public class FriendFamilyDTO {
3. long phoneNo;
4. long friendAndFamily;
5. public long getPhoneNo() {
6. return phoneNo;
7. }
8. public void setPhoneNo(long phoneNo) {
9. this.phoneNo = phoneNo;
10. }
11. public long getFriendAndFamily() {
12. return friendAndFamily;
13. }
14. public void setFriendAndFamily(long friendAndFamily) {
15. this.friendAndFamily = friendAndFamily;
16. }
17. public FriendFamilyDTO(long phoneNo, long friendAndFamily) {
18. this();
19. this.phoneNo = phoneNo;
20. this.friendAndFamily = friendAndFamily;
21. }
22. public FriendFamilyDTO() {
23. super();
24. }
25. @Override
26. public String toString() {
27. return "FriendFamilyDTO [phoneNo=" + phoneNo + ", friendAndFamily=" + friendAndFamily +
"]";
28. }
29. }
1. package com.infytel.dto;
2. import javax.xml.bind.annotation.XmlRootElement;
3. @XmlRootElement
4. public class PlanDTO {
5. Integer planId;
6. String planName;
7. Integer nationalRate;
8. Integer localRate;
9. public Integer getPlanId() {
10. return planId;
11. }
12. public void setPlanId(Integer planId) {
13. this.planId = planId;
14. }
15. public String getPlanName() {
16. return planName;
17. }
18. public void setPlanName(String planName) {
19. this.planName = planName;
20. }
21. public Integer getNationalRate() {
22. return nationalRate;
23. }
24. public void setNationalRate(Integer nationalRate) {
25. this.nationalRate = nationalRate;
26. }
27. public Integer getLocalRate() {
28. return localRate;
29. }
30. public void setLocalRate(Integer localRate) {
31. this.localRate = localRate;
32. }
33. public PlanDTO() {
34. super();
35. }
36. @Override
37. public String toString() {
38. return "PlanDTO [planId=" + planId + ", planName=" + planName + ", nationalRate=" +
nationalRate + ", localRate=" + localRate + "]";
39. }
40. }
41. }
1. package com.infytel.repository;
2. import java.util.ArrayList;
3. import java.util.List;
4. import javax.annotation.PostConstruct;
5. import org.springframework.stereotype.Repository;
6. import com.infytel.dto.CustomerDTO;
7. import com.infytel.dto.FriendFamilyDTO;
8. import com.infytel.dto.PlanDTO;
9. @Repository
10. public class CustomerRepository {
11. List<CustomerDTO> customers = null;
12. // populates customer in hard-coded way
13. @PostConstruct
14. public void initializer() {
15. CustomerDTO customerDTO = new CustomerDTO();
16. PlanDTO planDTO = new PlanDTO();
17. planDTO.setPlanId(1);
18. planDTO.setPlanName("Simple");
19. planDTO.setLocalRate(3);
20. planDTO.setNationalRate(5);
21. customerDTO.setAddress("Chennai");
22. customerDTO.setAge(18);
23. customerDTO.setCurrentPlan(planDTO);
24. customerDTO.setGender('m');
25. customerDTO.setName("Jack");
26. customerDTO.setEmail("[email protected]");
27. customerDTO.setPassword("ABC@123");
28. customerDTO.setPhoneNo(9951212222l);
29. List<FriendFamilyDTO> friendAndFamily = new ArrayList<>();
30. friendAndFamily.add(new FriendFamilyDTO(customerDTO.getPhoneNo(), 800000145));
31. friendAndFamily.add(new FriendFamilyDTO(customerDTO.getPhoneNo(), 700000145));
32. customerDTO.setFriendAndFamily(friendAndFamily);
33. customers = new ArrayList<>();
34. customers.add(customerDTO);
35. }
36. // creates customer
37. public String createCustomer(CustomerDTO customerDTO) {
38. customers.add(customerDTO);
39. return "Customer with" + customerDTO.getPhoneNo() + "added successfully";
40. }
1. package com.infytel.repository;
2. import java.util.ArrayList;
3. import java.util.Iterator;
4. import java.util.List;
5. import javax.annotation.PostConstruct;
6. import org.springframework.stereotype.Repository;
7. import com.infytel.dto.PlanDTO;
8. @Repository
9. public class PlanRepository {
10. private List<PlanDTO> plans;
11. // Populating a list of plans in hard-coded way
12. @PostConstruct
13. public void populatePlans() {
14. plans = new ArrayList<>();
15. PlanDTO plan1 = new PlanDTO();
16. plan1.setPlanId(1);
17. plan1.setPlanName("Simple");
18. plan1.setLocalRate(3);
19. plan1.setNationalRate(5);
20. plans.add(plan1);
21. PlanDTO plan2 = new PlanDTO();
22. plan2.setPlanId(2);
23. plan2.setPlanName("Medium");
24. plan2.setLocalRate(5);
25. plan2.setNationalRate(8);
26. plans.add(plan2);
27. }
28. // fetching plans
29. public List<PlanDTO> fetchPlans() {
30. return plans;
31. }
32. // feching plans based on localRates
33. public List<PlanDTO> plansLocalRate(List localRates) {
34. List<PlanDTO> plansResponse = new ArrayList<>();
35. Iterator it = localRates.iterator();
36. while (it.hasNext()) {
37. int rate = Integer.parseInt((String) it.next());
38. for (PlanDTO plan : plans) {
39. if (rate == plan.getLocalRate())
40. plansResponse.add(plan);
41. }
42. }
43. return plansResponse;
44. }
45. }
1. package com.infytel.service;
2. import java.time.LocalDate;
3. import java.util.List;
4. import org.springframework.beans.factory.annotation.Autowired;
5. import org.springframework.stereotype.Service;
6. import com.infytel.dto.CallDetailsDTO;
7. import com.infytel.repository.CallDetailsRepository;
8. @Service
9. public class CallDetailsService {
10. @Autowired
11. private CallDetailsRepository callDetailsRepository;
12. // contacts repository to fetch the call details
13. public List<CallDetailsDTO> fetchCallDetails(long calledBy, LocalDate calledOn) {
14. return callDetailsRepository.fetchCallDetails(calledBy, calledOn);
15. }
16. }
1. package com.infytel.service;
2. import java.util.List;
3. import org.springframework.beans.factory.annotation.Autowired;
4. import org.springframework.stereotype.Service;
5. import com.infytel.dto.CustomerDTO;
6. import com.infytel.repository.CustomerRepository;
7. @Service
8. public class CustomerService {
9. @Autowired
10. private CustomerRepository customerRepository;
11. // Contacts repository layer to add customer
12. public String createCustomer(CustomerDTO customerDTO) {
13. return customerRepository.createCustomer(customerDTO);
14. }
15. // Contacts repository layer to fetch customer
16. public List<CustomerDTO> fetchCustomer() {
17. return customerRepository.fetchCustomer();
18. }
19. // Contacts repository layer to delete customer
20. public String deleteCustomer(long phoneNumber) {
21. return customerRepository.deleteCustomer(phoneNumber);
22. }
23. // Contacts repository layer to update customer
24. public String updateCustomer(long phoneNumber, CustomerDTO customerDTO) {
25. return customerRepository.updateCustomer(phoneNumber, customerDTO);
26. }
27. }
1. package com.infytel.service;
2. import java.util.List;
3. import org.springframework.beans.factory.annotation.Autowired;
4. import org.springframework.stereotype.Service;
5. import com.infytel.dto.PlanDTO;
6. import com.infytel.repository.PlanRepository;
7. @Service
8. public class PlanService {
9. @Autowired
10. private PlanRepository planRepository;
11. // contacts repository to fetch plans
12. public List<PlanDTO> fetchPlans() {
13. return planRepository.fetchPlans();
14. }
15. // contacts repository to fetch plans by localRates
16. public List<PlanDTO> plansLocalRate(List localRates) {
17. return planRepository.plansLocalRate(localRates);
18. }
19. }
1. server.port = 8080
2. server.servlet.context-path=/infytel-5
Step 9: Make sure that the project's pom.xml looks similar to the one that is shown below.
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
5. <modelVersion>4.0.0</modelVersion>
6. <parent>
7. <groupId>org.springframework.boot</groupId>
8. <artifactId>spring-boot-starter-parent</artifactId>
9. <version>2.1.4.RELEASE</version>
10. <relativePath /> <!-- lookup parent from repository -->
11. </parent>
12. <groupId>infytel</groupId>
13. <artifactId>infytel_demo5</artifactId>
14. <version>0.0.1-SNAPSHOT</version>
15. <name>infytel_demo5</name>
16. <description>Demo project for Spring Boot</description>
17. <properties>
18. <java.version>1.8</java.version>
19. </properties>
20. <dependencies>
21. <dependency>
22. <groupId>org.springframework.boot</groupId>
23. <artifactId>spring-boot-starter-web</artifactId>
24. </dependency>
25. <dependency>
26. <groupId>org.springframework.boot</groupId>
27. <artifactId>spring-boot-starter-test</artifactId>
28. <scope>test</scope>
29. </dependency>
30. </dependencies>
31. <build>
32. <plugins>
33. <plugin>
34. <groupId>org.springframework.boot</groupId>
35. <artifactId>spring-boot-maven-plugin</artifactId>
36. </plugin>
37. </plugins>
38. </build>
39. </project>
Step 10: Deploy the application on the server by executing the class containing the main method.
So, we have successfully created and deployed the REST endpoints. Now, let us see how can we test the
same using Postman client.
Output
Note: There are two ways, the application can produce the data i.e., XML and JSON.
To get the response in XML format, enter the URL and select Headers and set the key as Accept and the
value as application/xml and click on send. Following will be the response generated,
To get the response in JSON format, enter the URL, select Headers and set the key as Accept and value as
application/json and click on send. Following will be the response generated,
Look at the records that are available in both the screenshots. Only the details of plans with localRate 3
and 5 are present.
This method,
Takes a list of vendor names (matrix variables)
Searches for the products of vendors whose names are received as matrix variables and renders the
list of products in XML format
Note:
1. You can implement this requirement in the project that you used for previous exercise.
2. You can simply hardcode the product details. It is optional to use a database to retrieve the product
details.
*Time given for this exercise indicates the time required to create the REST endpoint and is excluding the
time required to write the logic for retrieving the product details from database.
Handling exceptions will make sure that the entire stack trace is not thrown to the end-user which is very
hard to read and, possesses a lot of security risks as well. With proper exception handling routine, it is
possible for an application to send customized messages during failures and continue without being
terminated abruptly.
In simple words, we can say that exception handling makes any application robust.
@ExceptionHandler is a Spring annotation that plays a vital role in handling exceptions thrown out of
handler methods(Controller operations). This annotation can be used on the
Methods of a controller class. Doing so will help handle exceptions thrown out of the methods of
that specific controller alone.
Methods of classes that are annotated with @RestControllerAdvice. Doing so will make exception
handling global.
The most common way is applying @ExceptionHandler on methods of the class that is annotated with
@RestControllerAdvice. This will make the exceptions that are thrown from the controllers of the
application get handled in a centralized way. As well, there is no need for repeating the exception handling
code in all the controllers, keeping the code more manageable.
Example: An online telecom app called Infytel exposes its functionalities as RESTful resources. And,
CustomerController is one among such resources that deal with the customers of Infytel.
CustomerController has a method to delete a customer based on the phone number being passed.
What if the phoneNo does not exist and the code throws NoSuchCustomerException?
If no Exception handler is provided, Spring Boot provides a standard error message as shown below.
1. {
2. "timestamp": "2019-05-02T16:10:45.805+0000",
3. "status": 500,
4. "error": "Internal Server Error",
5. "message": "Customer does not exist :121",
6. "path": "/infytel-7/customers/121"
7. }
In case, a customized error message that is easy to understand has to be provided, we need to
Create a class annotated with @RestControllerAdvice
Have methods annotated with @ExceptionHandler(value=NameoftheException) which takes
the exception class as the value for which the method is the handler
We can have multiple methods in the Advice class to handle exceptions of different types and return the
custom messages accordingly.
Example: Below is the RestControllerAdvice class that holds a method which handles
NoSuchCustomerException by returning a customized error message.
1. @RestControllerAdvice
2. public class ExceptionControllerAdvice {
3. @ExceptionHandler(NoSuchCustomerException.class)
4. public ResponseEntity<String> exceptionHandler2(NoSuchCustomerException ex) {
5. return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
6. }
7. }
The object which is going to hold a custom error message is ErrorMessage with two variables, errorcode
and message.
1. public class ErrorMessage {
2. private int errorCode;
3. private String message;
4. //getters and setters go here
5. }
Here, the handler method returns the instance of ErrorMessage that holds the error code and message,
rather returning a String as the body of ResponseEntity.
Video
Objectives: To develop a Spring REST application that returns a graceful error message when the REST
endpoints encounter exceptions. We will learn,
How to handle Exceptions in a Spring REST application
Usage of @RestControllerAdvice annotation
Usge of @ExceptionHandler annotation
Scenario:
An online telecom app called Infytel wishes its functionalities to get exposed as RESTful services. It has
three controllers in total to deal with customer, plan and call details, among which, the focus will be on the
controller named CustomerController. Following HTTP operation of the CustomerController will be take
for discussion.
HTTP
Controller Class method URI Remarks
method
deleteCustomer() Deletes an existing customer
HTTP
CustomerContro throws /customers/{phoneNum by phoneNumber.If the
DELET
ller NoSuchCustomerExce ber} phoneNumber does not exist,
E
ption the method
throws NoSuchCustomerExce
ption
Steps:
Step 1: Create a Maven project using Spring Initializer with Web dependency and import the same in
STS.
Step 2: Modify the imported project according to the following project structure:
Step 3: Look at the class InfytelDemo6Application under com.infytel package that gets created
automatically when the project is generated. We will modify this only when we need to go with custom
configuration. Here, this class holds the code that is required to support matrix variables.
1. package com.infytel;
2. import org.springframework.boot.SpringApplication;
3. import org.springframework.boot.autoconfigure.SpringBootApplication;
4. import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
5. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
6. import org.springframework.web.util.UrlPathHelper;
7. @SpringBootApplication
8. public class InfytelDemo6Application implements WebMvcConfigurer {
9. public static void main(String[] args) {
10. SpringApplication.run(InfytelDemo6Application.class, args);
11. }
12. // To support matrix parameters
13. @Override
14. public void configurePathMatch(PathMatchConfigurer configurer) {
15. UrlPathHelper urlPathHelper = new UrlPathHelper();
16. urlPathHelper.setRemoveSemicolonContent(false);
17. configurer.setUrlPathHelper(urlPathHelper);
18. }
19. }
1. package com.infytel.controller;
2. import java.util.List;
3. import org.springframework.beans.factory.annotation.Autowired;
4. import org.springframework.http.ResponseEntity;
5. import org.springframework.web.bind.annotation.DeleteMapping;
6. import org.springframework.web.bind.annotation.GetMapping;
7. import org.springframework.web.bind.annotation.PathVariable;
8. import org.springframework.web.bind.annotation.PostMapping;
9. import org.springframework.web.bind.annotation.PutMapping;
10. import org.springframework.web.bind.annotation.RequestBody;
11. import org.springframework.web.bind.annotation.RequestMapping;
1. package com.infytel.controller;
2. import java.util.ArrayList;
3. import java.util.List;
4. import java.util.Map;
5. import java.util.Set;
6. import org.springframework.beans.factory.annotation.Autowired;
7. import org.springframework.web.bind.annotation.GetMapping;
8. import org.springframework.web.bind.annotation.MatrixVariable;
9. import org.springframework.web.bind.annotation.RequestMapping;
10. import org.springframework.web.bind.annotation.RestController;
11. import com.infytel.dto.EntityList;
12. import com.infytel.dto.PlanDTO;
13. import com.infytel.service.PlanService;
14. @RestController
15. @RequestMapping("/plans")
16. public class PlanController {
17. private EntityList<PlanDTO> plans;
18. @Autowired
19. private PlanService planService;
20. // Get all available plans
21. @GetMapping(produces = { "application/json", "application/xml" })
22. public EntityList<PlanDTO> fetchPlans() {
23. plans = new EntityList<>(planService.fetchPlans());
24. return plans;
25. }
26. // Gets plans based on localRate
27. @GetMapping(value = "/{query}/plan", produces = { "application/json", "application/xml" })
28. public EntityList<PlanDTO> plansLocalRate(@MatrixVariable(pathVar = "query") Map<String,
List<Integer>> map) {
29. Set<String> keysLocalRates = map.keySet();
30. ArrayList localRates = new ArrayList();
31. for (String key : keysLocalRates) {
32. for (int i = 0; i < map.get(key).size(); i++) {
33. localRates.add(map.get(key).get(i));
34. }
35. }
36. plans = new EntityList<>(planService.plansLocalRate(localRates));
37. return plans;
38. }
39. }
1. package com.infytel.dto;
2. import java.util.ArrayList;
3. import java.util.List;
4. import javax.xml.bind.annotation.XmlAnyElement;
5. import javax.xml.bind.annotation.XmlRootElement;
6. import javax.xml.bind.annotation.XmlSeeAlso;
7. @XmlRootElement
8. @XmlSeeAlso({ PlanDTO.class })
9. public class EntityList<T> {
10. private List<T> listOfEntityObjects;
11. public EntityList() {
12. listOfEntityObjects = new ArrayList<>();
13. }
14. public EntityList(List<T> listOfEntityObjects) {
15. this.listOfEntityObjects = listOfEntityObjects;
16. }
17. @XmlAnyElement
1. package com.infytel.dto;
2. public class ErrorMessage {
3. private int errorCode;
4. private String message;
5. public int getErrorCode() {
6. return errorCode;
7. }
8. public void setErrorCode(int errorCode) {
9. this.errorCode = errorCode;
10. }
11. public String getMessage() {
12. return message;
13. }
14. public void setMessage(String message) {
15. this.message = message;
16. }
17. }
1. package com.infytel.dto;
2. public class FriendFamilyDTO {
3. long phoneNo;
4. long friendAndFamily;
5. public long getPhoneNo() {
6. return phoneNo;
7. }
8. public void setPhoneNo(long phoneNo) {
9. this.phoneNo = phoneNo;
10. }
11. public long getFriendAndFamily() {
12. return friendAndFamily;
13. }
14. public void setFriendAndFamily(long friendAndFamily) {
15. this.friendAndFamily = friendAndFamily;
16. }
17. public FriendFamilyDTO(long phoneNo, long friendAndFamily) {
18. this();
1. package com.infytel.dto;
2. import javax.xml.bind.annotation.XmlRootElement;
3. @XmlRootElement
4. public class PlanDTO {
5. Integer planId;
6. String planName;
7. Integer nationalRate;
8. Integer localRate;
9. public Integer getPlanId() {
10. return planId;
11. }
12. public void setPlanId(Integer planId) {
13. this.planId = planId;
14. }
15. public String getPlanName() {
16. return planName;
17. }
18. public void setPlanName(String planName) {
19. this.planName = planName;
20. }
21. public Integer getNationalRate() {
22. return nationalRate;
23. }
24. public void setNationalRate(Integer nationalRate) {
25. this.nationalRate = nationalRate;
26. }
27. public Integer getLocalRate() {
28. return localRate;
29. }
1. package com.infytel.exceptions;
2. public class NoSuchCustomerException extends Exception {
3. private static final long serialVersionUID = 1L;
4. public NoSuchCustomerException() {
5. super();
6. }
37. callDetailsResultSet.add(callDetail);
38. }
39. return callDetailsResultSet;
40. }
41. }
1. package com.infytel.repository;
2. import java.util.ArrayList;
3. import java.util.List;
4. import javax.annotation.PostConstruct;
5. import org.springframework.stereotype.Repository;
6. import com.infytel.dto.CustomerDTO;
7. import com.infytel.dto.FriendFamilyDTO;
8. import com.infytel.dto.PlanDTO;
9. import com.infytel.exceptions.NoSuchCustomerException;
10. @Repository
11. public class CustomerRepository {
12. List<CustomerDTO> customers = null;
13. // Populates customer in hard-coded way
14. @PostConstruct
15. public void initializer() {
16. CustomerDTO customerDTO = new CustomerDTO();
17. PlanDTO planDTO = new PlanDTO();
18. planDTO.setPlanId(1);
19. planDTO.setPlanName("Simple");
20. planDTO.setLocalRate(3);
21. planDTO.setNationalRate(5);
22. customerDTO.setAddress("Chennai");
23. customerDTO.setAge(18);
24. customerDTO.setCurrentPlan(planDTO);
25. customerDTO.setGender('m');
26. customerDTO.setName("Jack");
27. customerDTO.setEmail("[email protected]");
28. customerDTO.setPassword("ABC@123");
29. customerDTO.setPhoneNo(9951212222l);
30. List<FriendFamilyDTO> friendAndFamily = new ArrayList<>();
31. friendAndFamily.add(new FriendFamilyDTO(customerDTO.getPhoneNo(), 800000145));
32. friendAndFamily.add(new FriendFamilyDTO(customerDTO.getPhoneNo(), 700000145));
33. customerDTO.setFriendAndFamily(friendAndFamily);
34. customers = new ArrayList<>();
35. customers.add(customerDTO);
36. }
1. package com.infytel.repository;
2. import java.util.ArrayList;
3. import java.util.Iterator;
4. import java.util.List;
5. import javax.annotation.PostConstruct;
6. import org.springframework.stereotype.Repository;
7. import com.infytel.dto.PlanDTO;
8. @Repository
9. public class PlanRepository {
10. private List<PlanDTO> plans;
11. // Populating a list of plans
12. @PostConstruct
13. public void populatePlans() {
14. plans = new ArrayList<>();
15. PlanDTO plan1 = new PlanDTO();
16. plan1.setPlanId(1);
17. plan1.setPlanName("Simple");
18. plan1.setLocalRate(3);
19. plan1.setNationalRate(5);
20. plans.add(plan1);
21. PlanDTO plan2 = new PlanDTO();
22. plan2.setPlanId(2);
23. plan2.setPlanName("Medium");
24. plan2.setLocalRate(5);
25. plan2.setNationalRate(8);
26. plans.add(plan2);
27. }
28. // fetching all available plans
29. public List<PlanDTO> fetchPlans() {
30. return plans;
31. }
32. // fetching plans based on localRate
33. public List<PlanDTO> plansLocalRate(List localRates) {
34. List<PlanDTO> plansResponse = new ArrayList<>();
35. Iterator it = localRates.iterator();
36. while (it.hasNext()) {
37. int rate = Integer.parseInt((String) it.next());
38. for (PlanDTO plan : plans) {
1. package com.infytel.service;
2. import java.util.List;
3. import org.springframework.beans.factory.annotation.Autowired;
4. import org.springframework.stereotype.Service;
5. import com.infytel.dto.CustomerDTO;
6. import com.infytel.exceptions.NoSuchCustomerException;
7. import com.infytel.repository.CustomerRepository;
8. @Service
9. public class CustomerService {
10. @Autowired
11. private CustomerRepository customerRepository;
12. // Contacts repository layer to add customer
13. public String createCustomer(CustomerDTO customerDTO) {
14. return customerRepository.createCustomer(customerDTO);
15. }
1. package com.infytel.service;
2. import java.util.List;
3. import org.springframework.beans.factory.annotation.Autowired;
4. import org.springframework.stereotype.Service;
5. import com.infytel.dto.PlanDTO;
6. import com.infytel.repository.PlanRepository;
7. @Service
8. public class PlanService {
9. @Autowired
10. private PlanRepository planRepository;
11. // contacts repository to fetch plans
12. public List<PlanDTO> fetchPlans() {
13. return planRepository.fetchPlans();
14. }
15. // contacts repository to fetch plans by localRates
16. public List<PlanDTO> plansLocalRate(List localRates) {
17. return planRepository.plansLocalRate(localRates);
18. }
19. }
Step 10: Make sure that the project's pom.xml looks similar to the one shown below.
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
5. <modelVersion>4.0.0</modelVersion>
6. <parent>
7. <groupId>org.springframework.boot</groupId>
8. <artifactId>spring-boot-starter-parent</artifactId>
9. <version>2.1.4.RELEASE</version>
10. <relativePath /> <!-- lookup parent from repository -->
11. </parent>
12. <groupId>infytel</groupId>
13. <artifactId>infytel_demo7</artifactId>
14. <version>0.0.1-SNAPSHOT</version>
15. <name>infytel_demo7</name>
16. <description>Demo project for Spring Boot</description>
17. <properties>
18. <java.version>1.8</java.version>
19. </properties>
20. <dependencies>
21. <dependency>
22. <groupId>org.springframework.boot</groupId>
23. <artifactId>spring-boot-starter-web</artifactId>
24. </dependency>
25. <dependency>
26. <groupId>org.springframework.boot</groupId>
27. <artifactId>spring-boot-starter-test</artifactId>
28. <scope>test</scope>
29. </dependency>
30. </dependencies>
31. <build>
32. <plugins>
33. <plugin>
34. <groupId>org.springframework.boot</groupId>
35. <artifactId>spring-boot-maven-plugin</artifactId>
36. </plugin>
37. </plugins>
38. </build>
39. </project>
Step 11: Deploy the application on the server by executing the class containing the main method.
So, we have successfully created and deployed the REST endpoints. Now, let us see how can we test the
same using Postman client.
Output
Testing Web Service using Postman
Step 1: Launch Postman.
Step 2: Test this URL - http://localhost:8080/infytel-6/customers/111111111 using HTTP DELETE
that will delete the customer based on the phone number (invalid phoneNumber that does not exist in
Infytel) being passed.
Observe the message that is sent when we try to perform delete operation with an invalid phoneNumber.
Step 3: Test this URL - http://localhost:8080/infytel-6/customers/9951212222 using HTTP DELETE
that will delete the customer based on the phone number (valid phoneNumber that exists in Infytel) being
passed.
The customer of valid phone number is deleted successfully and the same is intimated to the client back.
CookPick online grocery application has a requirement where the admin should be able to delete a product
based on its product code.
Write a RESTful endpoint that would serve this purpose. Also, handle the code in such a way that it
renders a customized error message when the product is not found.
RESTful URL HTTP Action Business Operation
/product/{productCode} DELETE deleteProduct()
This method,
Note:
1. You can implement this requirement in the project that you used for previous exercise.
2. You can simply hardcode sample product details in a collection and delete the product details from the
collection in your logic. It is optional to use a database to delete the product details.
*Time given for this exercise indicates the time required to create the REST endpoint and is excluding the
time required to write the logic for deleting the product details from database.
Some times, the RESTful web service might need data in certain standard that should pass through
specific constraints.
What if the data is not in the format as needed by the RESTful service?
Some times, we might send valid format and structure, but with missing data. Still, the request gets
processed. In such situations, it becomes even more important to validate the data.
Example:
1. {
2. //empty JSON data
3. }
Hibernate Validator, is one of the implementations of the bean validation API. Let us see, how to use the
Validation API to validate our incoming data.
Bean Validation API provides a number of annotations and most of these annotations are self
explanatory.
Digits
Email
Max
Min
NotEmpty
NotNull
Null
Pattern
Let us apply validation on the customer data that comes to the createCustomer() method of
CustomerController. Infytel expects the name field not to be null and the email field to be of proper email
format.
1. public class CustomerDTO {
2. long phoneNo;
3. @NotNull
4. String name;
5. @Email(message = "Email id is not in format, please check")
6. String email;
7. int age;
8. char gender;
9. List<FriendFamilyDTO> friendAndFamily;
10. String password;
11. String address;
12. PlanDTO currentPlan;
13. // getter and setters go here
14. }
Have a look at the usage of @NotNull and @Email on the fileds, name and email respectively.
Adding the constraints simply on the bean will not carry out validation. In addition, we need to mention
that validation is required while the JSON data is deserialized to CustomerDTO and get the validation
error, if any, into org.springframework.validation.Errors object. This can be done by applying @Valid
annotation on the handler method argument which captures the CustomerDTO object as shown below.We
can inject the Errors object too into this method.
1. @PostMapping(consumes="application/json")
2. public ResponseEntity createCustomer(@Valid @RequestBody CustomerDTO customerDTO,
Errors errors)
3. {
4. String response = "";
5. if (errors.hasErrors())
6. {
7. response = errors.getAllErrors().stream()
8. .map(ObjectError::getDefaultMessage)
9. .collect(Collectors.joining(","));
10. ErrorMessage error = new ErrorMessage();
11. error.setErrorCode(HttpStatus.NOT_ACCEPTABLE.value());
12. error.setMessage(response);
13. return ResponseEntity.ok(error);
14. }
15. else
16. {
17. response = customerService.createCustomer(customerDTO);
18. return ResponseEntity.ok(response);
19. }
20. }
Finally, the business logic will be divided into two courses of actions. That is, what should be done when
there are no validation errors. And, what should be done, otherwise.
Set the String that contains errors in the message field and appropriate error code in the errorCode field of
ErrorMessage.
Finally, return the ResponseEntity that is set with the ErrorMessage object.
1. if (errors.hasErrors())
2. {
3. response = errors.getAllErrors().stream().
4. map(x->x.getDefaultMessage()).
5. collect(Collectors.joining(","));
6. ErrorMessage error = new ErrorMessage();
7. error.setErrorCode(HttpStatus.NOT_ACCEPTABLE.value());
8. error.setMessage(response);
9. return ResponseEntity.ok(error);
10. }
2. If there are no errors, invoke the service layer to create the customer and return the response back to the
client.
1. response = customerService.createCustomer(customerDTO);
2. return ResponseEntity.ok(response);
We are done dealing with validating the incoming objects/DTOs. Also, we finished traversing a set of
annotations that impose validation on the individual fields/attributes of the DTO.
Apart from the annotations that we discussed, we have few other interesting annotations in place. For
example,
@PastOrPresent
@Past
@Future
@FutureOrPresent
@DateTimeFormat - part of org.springframework.format.annotation unlike other annotations being
mentioned here which are part of standard javax.validation.constraints
These annotations can only be applied on the fields/attributes of type, date/time, for example,
LocalDate/LocalTime.
One more interesting annotation that needs a mention here is @Positive which is used to make sure a
number should possess only positive values for example, cost of an article.
Last but not the least to be brought in for discussion is @NotBlank which makes sure that the string is not
null and the trimmed length should be greater than zero.
Further Reading : Explore javax.validations.constraints API to get the complete list of validation
annotations that can be applied on the fields/attributes.
Like how it is important to validate the incoming objects/DTOs, it is essential to validate the incoming
URI parameters as well. For example, when the customer details need to be deleted based on the phone
number and if the same is reaching the REST endpoint as URI parameter (path variable, for example), it
becomes essential that the URI parameter, phone number should also be validated (should be of 10 digits,
for example).
The code snippet given below helps serving the purpose discussed above.
1. public String deleteCustomer(@PathVariable("phoneNumber")
2. @Pattern(regexp = "[0-9]{10}",message="{customer.phoneNo.invalid}")
3. String phoneNumber) throws NoSuchCustomerException
Here, the message that should be rendered during the validation failure need not be hard-coded always. It
can be fetched from the external property file as well and the name of the file is
ValidationMessages.properties that Spring Boot searches for. If the file name is other than
ValidationMessages.properties, MessageSource and LocalValidatorFactoryBean need to be configured in
the bootstrap class.
One important thing that needs attention while validating the URI parameters is, @Validated.
The controller where the URI parameter validation is carried out should be annotated with @Validated.
The validation on URI parameters will not be triggered, otherwise. Here, the CustomerController has
validations pertaining to phoneNo that is received as URI parameter (DELETE and UPDATE). So,
@Validated annotation needs to be applied on the same as follows.
1. @Validated
2. public class CustomerController
So, the code that should be reached out during validation failures should also be made centralized as how
we did for exceptions. We can enhance the ExceptionControllerAdvice class which is annotated
with @RestControllerAdvice by adding exception handlers for
MethodArgumentNotValidException - Validation failures in DTOs
ConstraintViolationException - Validation failures in URI parameters
Have a look at the code snippet below to understand the concept better.
1. //validation failures on DTOs
2. @ExceptionHandler(MethodArgumentNotValidException.class)
3. public ResponseEntity<ErrorMessage> handleValidationExceptions(
4. MethodArgumentNotValidException ex) {
5. ErrorMessage error = new ErrorMessage();
6. error.setErrorCode(HttpStatus.BAD_REQUEST.value());
7. error.setMessage(ex.getBindingResult().getAllErrors().stream()
8. .map(ObjectError::getDefaultMessage)
9. .collect(Collectors.joining(", ")));
10. return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
11. }
12. //Validation failures on URI parameters
13. @ExceptionHandler(ConstraintViolationException.class)
14. public ResponseEntity<ErrorMessage> handleConstraintValidationExceptions(
15. ConstraintViolationException ex) {
16. ErrorMessage error = new ErrorMessage();
17. error.setErrorCode(HttpStatus.BAD_REQUEST.value());
18. error.setMessage(ex.getConstraintViolations().stream()
19. .map(ConstraintViolation::getMessage)
20. .collect(Collectors.joining(", ")));
21. return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
22. }
Objectives: To create a Spring REST controller, where the data being received is first validated, and then
processed. If the data being received is not of the expected specifications, a Custom Error Message will
be sent by the controller.
Scenario: An online telecom app called Infytel wishes its functionalities to get exposed as RESTful
services. It has three controllers in total to deal with customer, plan and call details, among which, the
focus will be on the controller named CustomerController that has a service method that validates the
incoming data and sends customized error message to the client back, during validation failures. Here
follows the method details,
HTTP
Controller class method URI Remarks
Method
This will create a customer in Infytel
CustomerController createCustomer /customers POST provided, the email address is in proper email
format and the name is not null as well.
CustomerDTO has used @Email and @NotNull annotations
The special ErrorMessage POJO class
Steps:
Step 1: Create a Maven project using Spring Initializer with web dependency and import the same in STS.
Step 2: Modify the imported project according to the following project structure:
Step 3: Look at the class InfytelDemo7Application under com.infytel package that gets created
automatically when the project is generated. We will modify this only when we need to go with custom
configuration. This class holds the code for enabling matrix variables as extra configuration.
1. package com.infytel;
2. import org.springframework.boot.SpringApplication;
3. import org.springframework.boot.autoconfigure.SpringBootApplication;
4. import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
5. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
6. import org.springframework.web.util.UrlPathHelper;
7. @SpringBootApplication
8. public class InfytelDemo7Application implements WebMvcConfigurer {
9. public static void main(String[] args) {
1. package com.infytel.controller;
2. import java.util.List;
3. import java.util.stream.Collectors;
4. import javax.validation.Valid;
5. import org.springframework.beans.factory.annotation.Autowired;
6. import org.springframework.http.HttpStatus;
7. import org.springframework.http.ResponseEntity;
8. import org.springframework.validation.Errors;
9. import org.springframework.validation.ObjectError;
10. import org.springframework.web.bind.annotation.DeleteMapping;
11. import org.springframework.web.bind.annotation.GetMapping;
12. import org.springframework.web.bind.annotation.PathVariable;
13. import org.springframework.web.bind.annotation.PostMapping;
14. import org.springframework.web.bind.annotation.PutMapping;
15. import org.springframework.web.bind.annotation.RequestBody;
16. import org.springframework.web.bind.annotation.RequestMapping;
17. import org.springframework.web.bind.annotation.RestController;
18. import com.infytel.dto.CustomerDTO;
19. import com.infytel.dto.ErrorMessage;
20. import com.infytel.exceptions.NoSuchCustomerException;
21. import com.infytel.service.CustomerService;
22. @RestController
23. @RequestMapping("/customers")
24. public class CustomerController {
25. @Autowired
26. private CustomerService customerService;
27. // Fetching customer details
28. @GetMapping(produces = "application/json")
29. public List<CustomerDTO> fetchCustomer() {
30. return customerService.fetchCustomer();
31. }
32. // Adding a customer
33. @PostMapping(consumes = "application/json")
34. public ResponseEntity createCustomer(@Valid @RequestBody CustomerDTO customerDTO,
Errors errors) {
35. String response = "";
36. if (errors.hasErrors()) {
37. // collecting the validation errors of all fields together in a String delimited
38. // by commas
39. response = errors.getAllErrors().stream().map(ObjectError::getDefaultMessage)
40. .collect(Collectors.joining(","));
41. ErrorMessage error = new ErrorMessage();
42. error.setErrorCode(HttpStatus.NOT_ACCEPTABLE.value());
43. error.setMessage(response);
44. return ResponseEntity.ok(error);
45. } else {
46. response = customerService.createCustomer(customerDTO);
47. return ResponseEntity.ok(response);
48. }
49. }
50. // Updating an existing customer
51. @PutMapping(value = "/{phoneNumber}", consumes = "application/json")
52. public String updateCustomer(@PathVariable("phoneNumber") long phoneNumber,
@RequestBody CustomerDTO customerDTO) {
53. return customerService.updateCustomer(phoneNumber, customerDTO);
54. }
55. // Deleting a customer
56. @DeleteMapping(value = "/{phoneNumber}", produces = "text/html")
57. public String deleteCustomer(@PathVariable("phoneNumber") long phoneNumber) throws
NoSuchCustomerException {
58. return customerService.deleteCustomer(phoneNumber);
59. }
60. }
1. package com.infytel.controller;
2. import java.util.ArrayList;
3. import java.util.List;
4. import java.util.Map;
5. import java.util.Set;
6. import org.springframework.beans.factory.annotation.Autowired;
7. import org.springframework.web.bind.annotation.GetMapping;
8. import org.springframework.web.bind.annotation.MatrixVariable;
9. import org.springframework.web.bind.annotation.RequestMapping;
10. import org.springframework.web.bind.annotation.RestController;
11. import com.infytel.dto.EntityList;
12. import com.infytel.dto.PlanDTO;
13. import com.infytel.service.PlanService;
14. @RestController
15. @RequestMapping("/plans")
16. public class PlanController {
17. private EntityList<PlanDTO> plans;
18. @Autowired
19. private PlanService planService;
20. // Get all available plans
21. @GetMapping(produces = { "application/json", "application/xml" })
22. public EntityList<PlanDTO> fetchPlans() {
23. plans = new EntityList<>(planService.fetchPlans());
1. package com.infytel.dto;
2. import java.util.List;
3. import javax.validation.constraints.Email;
4. import javax.validation.constraints.NotNull;
5. public class CustomerDTO {
6. long phoneNo;
7. @NotNull(message = "Name cannot be empty")
8. String name;
9. @Email(message = "Email id is not in format, please check")
10. String email;
11. public String getEmail() {
12. return email;
13. }
14. public void setEmail(String email) {
15. this.email = email;
16. }
17. int age;
18. char gender;
19. List<FriendFamilyDTO> friendAndFamily;
20. String password;
21. String address;
22. PlanDTO currentPlan;
23. public PlanDTO getCurrentPlan() {
24. return currentPlan;
25. }
26. public void setCurrentPlan(PlanDTO currentPlan) {
1. package com.infytel.dto;
2. import java.util.ArrayList;
3. import java.util.List;
4. import javax.xml.bind.annotation.XmlAnyElement;
5. import javax.xml.bind.annotation.XmlRootElement;
6. import javax.xml.bind.annotation.XmlSeeAlso;
7. @XmlRootElement
8. @XmlSeeAlso({ PlanDTO.class })
9. public class EntityList<T> {
10. private List<T> listOfEntityObjects;
11. public EntityList() {
12. listOfEntityObjects = new ArrayList<>();
13. }
14. public EntityList(List<T> listOfEntityObjects) {
15. this.listOfEntityObjects = listOfEntityObjects;
16. }
17. @XmlAnyElement
18. public List<T> getItems() {
19. return listOfEntityObjects;
20. }
21. }
1. package com.infytel.dto;
2. public class ErrorMessage {
3. private int errorCode;
4. private String message;
5. public int getErrorCode() {
6. return errorCode;
7. }
8. public void setErrorCode(int errorCode) {
9. this.errorCode = errorCode;
10. }
11. public String getMessage() {
12. return message;
13. }
14. public void setMessage(String message) {
15. this.message = message;
16. }
17. }
1. package com.infytel.dto;
2. public class FriendFamilyDTO {
3. long phoneNo;
4. long friendAndFamily;
5. public long getPhoneNo() {
6. return phoneNo;
7. }
8. public void setPhoneNo(long phoneNo) {
9. this.phoneNo = phoneNo;
10. }
11. public long getFriendAndFamily() {
12. return friendAndFamily;
13. }
14. public void setFriendAndFamily(long friendAndFamily) {
15. this.friendAndFamily = friendAndFamily;
16. }
17. public FriendFamilyDTO(long phoneNo, long friendAndFamily) {
18. this();
19. this.phoneNo = phoneNo;
20. this.friendAndFamily = friendAndFamily;
21. }
22. public FriendFamilyDTO() {
23. super();
24. }
25. @Override
26. public String toString() {
27. return "FriendFamilyDTO [phoneNo=" + phoneNo + ", friendAndFamily=" + friendAndFamily +
"]";
28. }
29. }
1. package com.infytel.dto;
2. import javax.xml.bind.annotation.XmlRootElement;
3. @XmlRootElement
4. public class PlanDTO {
5. Integer planId;
6. String planName;
7. Integer nationalRate;
8. Integer localRate;
9. public Integer getPlanId() {
10. return planId;
11. }
12. public void setPlanId(Integer planId) {
13. this.planId = planId;
14. }
15. public String getPlanName() {
16. return planName;
17. }
18. public void setPlanName(String planName) {
19. this.planName = planName;
20. }
21. public Integer getNationalRate() {
22. return nationalRate;
23. }
24. public void setNationalRate(Integer nationalRate) {
25. this.nationalRate = nationalRate;
26. }
27. public Integer getLocalRate() {
28. return localRate;
29. }
30. public void setLocalRate(Integer localRate) {
31. this.localRate = localRate;
32. }
33. public PlanDTO() {
34. super();
35. }
36. @Override
37. public String toString() {
38. return "PlanDTO [planId=" + planId + ", planName=" + planName + ", nationalRate=" +
nationalRate + ", localRate=" + localRate + "]";
39. }
40. }
1. package com.infytel.exceptions;
2. public class NoSuchCustomerException extends Exception {
3. private static final long serialVersionUID = 1L;
4. public NoSuchCustomerException() {
5. super();
6. }
7. public NoSuchCustomerException(String errors) {
8. super(errors);
9. }
10. }
7. import com.infytel.dto.CallDetailsDTO;
8. @Repository
9. public class CallDetailsRepository {
10. List<CallDetailsDTO> callDetails = null;
11. CallDetailsDTO callDetailsDTO = null;
12. CallDetailsDTO callDetailsDTO1 = null;
13. LocalDate calledOn = null;
14. // populates call details in hard-coded way
15. @PostConstruct
16. public void populatecalledOn() {
17. callDetails = new ArrayList<>();
18. callDetailsDTO = new CallDetailsDTO();
19. callDetailsDTO1 = new CallDetailsDTO();
20. calledOn = LocalDate.now();
21. callDetailsDTO.setCalledBy(8870106465l);
22. callDetailsDTO.setCalledTo(9930508495l);
23. callDetailsDTO.setCalledOn(calledOn);
24. callDetailsDTO.setDuration(3);
25. callDetailsDTO1.setCalledBy(8870106465l);
26. callDetailsDTO1.setCalledTo(9930508495l);
27. callDetailsDTO1.setCalledOn(calledOn);
28. callDetailsDTO1.setDuration(5);
29. callDetails.add(callDetailsDTO);
30. callDetails.add(callDetailsDTO1);
31. }
32. // fetching call details based on calledBy and calledOn attributes
33. public List<CallDetailsDTO> fetchCallDetails(long calledBy, LocalDate calledOn) {
34. List<CallDetailsDTO> callDetailsResultSet = new ArrayList<>();
35. for (CallDetailsDTO callDetail : callDetails) {
36. if (callDetail.getCalledBy() == calledBy && callDetail.getCalledOn().equals(calledOn))
37. callDetailsResultSet.add(callDetail);
38. }
39. return callDetailsResultSet;
40. }
41. }
1. package com.infytel.repository;
2. import java.util.ArrayList;
3. import java.util.List;
4. import javax.annotation.PostConstruct;
5. import org.springframework.stereotype.Repository;
6. import com.infytel.dto.CustomerDTO;
7. import com.infytel.dto.FriendFamilyDTO;
8. import com.infytel.dto.PlanDTO;
9. import com.infytel.exceptions.NoSuchCustomerException;
10. @Repository
11. public class CustomerRepository {
12. List<CustomerDTO> customers = null;
13. // Populates customer in hard-coded way
14. @PostConstruct
15. public void initializer() {
16. CustomerDTO customerDTO = new CustomerDTO();
17. PlanDTO planDTO = new PlanDTO();
18. planDTO.setPlanId(1);
19. planDTO.setPlanName("Simple");
20. planDTO.setLocalRate(3);
21. planDTO.setNationalRate(5);
22. customerDTO.setAddress("Chennai");
23. customerDTO.setAge(18);
24. customerDTO.setCurrentPlan(planDTO);
25. customerDTO.setGender('m');
26. customerDTO.setName("Jack");
27. customerDTO.setEmail("[email protected]");
28. customerDTO.setPassword("ABC@123");
29. customerDTO.setPhoneNo(9951212222l);
30. List<FriendFamilyDTO> friendAndFamily = new ArrayList<>();
31. friendAndFamily.add(new FriendFamilyDTO(customerDTO.getPhoneNo(), 800000145));
32. friendAndFamily.add(new FriendFamilyDTO(customerDTO.getPhoneNo(), 700000145));
33. customerDTO.setFriendAndFamily(friendAndFamily);
34. customers = new ArrayList<>();
35. customers.add(customerDTO);
36. }
37. // creates customer
38. public String createCustomer(CustomerDTO customerDTO) {
39. customers.add(customerDTO);
40. return "Customer with" + customerDTO.getPhoneNo() + "added successfully";
41. }
42. // fetches customer
43. public List<CustomerDTO> fetchCustomer() {
44. return customers;
45. }
46. // deletes customer - exception handling incorporated
47. public String deleteCustomer(long phoneNumber) throws NoSuchCustomerException {
48. boolean notfound = true;
49. String response = "Customer of:" + phoneNumber + "\t does not exist";
50. for (CustomerDTO customer : customers) {
51. if (customer.getPhoneNo() == phoneNumber) {
52. customers.remove(customer);
53. response = customer.getName() + " with phoneNumber " + customer.getPhoneNo() + " deleted
successfully";
54. notfound = false;
55. break;
56. }
57. }
58. if (notfound)
59. throw new NoSuchCustomerException("Customer does not exist :" + phoneNumber);
60. return response;
61. }
62. // updates customer
63. public String updateCustomer(long phoneNumber, CustomerDTO customerDTO) {
64. String response = "Customer of:" + phoneNumber + "\t does not exist";
65. for (CustomerDTO customer : customers) {
66. if (customer.getPhoneNo() == phoneNumber) {
67. if (customerDTO.getName() != null)
68. customer.setName(customerDTO.getName());
69. if (customerDTO.getAddress() != null)
70. customer.setAddress(customerDTO.getAddress());
71. if (customerDTO.getPassword() != null)
72. customer.setPassword(customerDTO.getPassword());
73. customers.set(customers.indexOf(customer), customer);
74. response = "Customer of phoneNumber" + customer.getPhoneNo() + "\t got updated successfully";
75. break;
76. }
77. }
78. return response;
79. }
80. }
1. package com.infytel.repository;
2. import java.util.ArrayList;
3. import java.util.Iterator;
4. import java.util.List;
5. import javax.annotation.PostConstruct;
6. import org.springframework.stereotype.Repository;
7. import com.infytel.dto.PlanDTO;
8. @Repository
3. import java.util.List;
4. import org.springframework.beans.factory.annotation.Autowired;
5. import org.springframework.stereotype.Service;
6. import com.infytel.dto.CallDetailsDTO;
7. import com.infytel.repository.CallDetailsRepository;
8. @Service
9. public class CallDetailsService {
10. @Autowired
11. private CallDetailsRepository callDetailsRepository;
12. // contacts repository to fetch the call details
13. public List<CallDetailsDTO> fetchCallDetails(long calledBy, LocalDate calledOn) {
14. return callDetailsRepository.fetchCallDetails(calledBy, calledOn);
15. }
16. }
1. package com.infytel.service;
2. import java.util.List;
3. import org.springframework.beans.factory.annotation.Autowired;
4. import org.springframework.stereotype.Service;
5. import com.infytel.dto.CustomerDTO;
6. import com.infytel.exceptions.NoSuchCustomerException;
7. import com.infytel.repository.CustomerRepository;
8. @Service
9. public class CustomerService {
10. @Autowired
11. private CustomerRepository customerRepository;
12. // Contacts repository layer to add customer
13. public String createCustomer(CustomerDTO customerDTO) {
14. return customerRepository.createCustomer(customerDTO);
15. }
16. // Contacts repository layer to fetch customer
17. public List<CustomerDTO> fetchCustomer() {
18. return customerRepository.fetchCustomer();
19. }
20. // Contacts repository layer to delete customer
21. public String deleteCustomer(long phoneNumber) throws NoSuchCustomerException {
22. return customerRepository.deleteCustomer(phoneNumber);
23. }
24. // Contacts repository layer to update customer
25. public String updateCustomer(long phoneNumber, CustomerDTO customerDTO) {
26. return customerRepository.updateCustomer(phoneNumber, customerDTO);
27. }
28. }
1. package com.infytel.service;
2. import java.util.List;
3. import org.springframework.beans.factory.annotation.Autowired;
4. import org.springframework.stereotype.Service;
5. import com.infytel.dto.PlanDTO;
6. import com.infytel.repository.PlanRepository;
7. @Service
8. public class PlanService {
9. @Autowired
10. private PlanRepository planRepository;
11. // contacts repository to fetch plans
12. public List<PlanDTO> fetchPlans() {
13. return planRepository.fetchPlans();
14. }
15. // contacts repository to fetch plans by localRates
16. public List<PlanDTO> plansLocalRate(List localRates) {
17. return planRepository.plansLocalRate(localRates);
18. }
19. }
Step 10: Make sure that the project's pom.xml looks similar to the one shown below.
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
5. <modelVersion>4.0.0</modelVersion>
6. <parent>
7. <groupId>org.springframework.boot</groupId>
8. <artifactId>spring-boot-starter-parent</artifactId>
9. <version>2.1.4.RELEASE</version>
10. <relativePath /> <!-- lookup parent from repository -->
11. </parent>
12. <groupId>com.infytel</groupId>
13. <artifactId>infytel_demo7</artifactId>
14. <version>0.0.1-SNAPSHOT</version>
15. <name>infytel_demo7</name>
16. <description>Demo project for Spring Boot</description>
17. <properties>
18. <java.version>1.8</java.version>
19. </properties>
20. <dependencies>
21. <dependency>
22. <groupId>org.springframework.boot</groupId>
23. <artifactId>spring-boot-starter-web</artifactId>
24. </dependency>
25. <dependency>
26. <groupId>org.springframework.boot</groupId>
27. <artifactId>spring-boot-starter-test</artifactId>
28. <scope>test</scope>
29. </dependency>
30. </dependencies>
31. <build>
32. <plugins>
33. <plugin>
34. <groupId>org.springframework.boot</groupId>
35. <artifactId>spring-boot-maven-plugin</artifactId>
36. </plugin>
37. </plugins>
38. </build>
39. </project>
Step 11: Deploy the application on the server by executing the class containing the main method.
So, we have successfully created and deployed REST endpoints. Now, let us see how can we test the same
using Postman client.
Output
Step 2: Test this URL - http://localhost:8080/infytel-7/customers using HTTP POST. Submit the
request with customer data in JSON format. Try giving an invalid email id for the customer. The reason
for failure along with the status code will be generated as response.
Note: Now, post another customer data with proper email id and feel the difference.
Objectives: To create a Spring REST controller, where the data being received is first validated, and then
processed. If the data being received is not of the expected specifications, a custom error message will
be sent to the client with the help of a centralized handler.
Scenario: An online telecom app called Infytel wishes its functionalities to get exposed as RESTful
services. It has three controllers in total to deal with customer, plan and call details among which, the
focus will be on the controller named CustomerController that has service methods that validate the
incoming data and send customized error message back to the client during validation failures. Here
follows the details of the methods,
HTTP
Controller class Method URI Remarks
Method
CustomerController createCustomer /customers POST This will create a customer in
Step 3: Look at the class InfytelValidationApplication in com.infytel package that gets created
automatically when the project is generated. We will modify this only when we need to go with custom
configuration.
1. package com.infytel;
2. import org.springframework.boot.SpringApplication;
3. import org.springframework.boot.autoconfigure.SpringBootApplication;
4. @SpringBootApplication
5. public class InfytelValidationApplication {
6. public static void main(String[] args) {
7. SpringApplication.run(InfytelValidationApplication.class, args);
8. }
9. }
Step 5: Create the DTO classes CustomerDTO, FriendFamilyDTO and PlanDTO under
com.infytel.dto package
1. package com.infytel.dto;
2. import java.util.List;
3. import javax.validation.constraints.Email;
4. import javax.validation.constraints.Max;
5. import javax.validation.constraints.Min;
6. import javax.validation.constraints.NotBlank;
7. import javax.validation.constraints.NotEmpty;
8. import javax.validation.constraints.NotNull;
9. import javax.validation.constraints.Pattern;
10. public class CustomerDTO
11. {
12. @NotNull(message="{customer.phone.must}")
13. Long phoneNo;
14. @NotBlank(message="{customer.name.must}")
15. String name;
16. //Password should comprise of alphabets of both the cases and digits as well with a length of
minimum 5
17. @NotEmpty(message="{customer.password.must}")
18. @Pattern(regexp="^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=\\S+$).{5,}$",message=
"{customer.password.invalid}")
19. String password;
20. @NotNull(message="{customer.email.must}")
21. @Email(message= "{customer.email.invalid}")
22. String email;
23. @Min(value=18, message = "{customer.age.invalid}")
24. @Max(value=60, message = "{customer.age.invalid}")
28. customerDTO.setEmail("[email protected]");
29. customerDTO.setPassword("ABC@123");
30. customerDTO.setPhoneNo(9951212222l);
31. List<FriendFamilyDTO> friendAndFamily = new ArrayList<>();
32. friendAndFamily.add(new FriendFamilyDTO(customerDTO.getPhoneNo(),800000145));
33. friendAndFamily.add(new FriendFamilyDTO(customerDTO.getPhoneNo(),700000145));
34. customerDTO.setFriendAndFamily(friendAndFamily);
35. customers = new ArrayList<>();
36. customers.add(customerDTO);
37. }
38. //creates customer
39. public String createCustomer(CustomerDTO customerDTO)
40. {
41. customers.add(customerDTO);
42. return "Customer details got added successfully";
43. //since this code deals with a hard-coded list and not the actual repository, return string gets hard-
coded here.
44. //The service layer will deal with the exception or the response, otherwise.
45. //In such cases, its preferred to make the exception and the success message available in the
ValidationMessages.properties
46. //And, these messages can be taken as how the methods of CustomerService do
47. }
48. //fetches customer
49. public List<CustomerDTO> fetchCustomer()
50. {
51. return customers;
52. }
53. //deletes customer - exception handling incorporated
54. public boolean deleteCustomer(long phoneNumber)
55. {
56. boolean responseDelete=false;
57. for(CustomerDTO customer : customers)
58. {
59. if(customer.getPhoneNo() == phoneNumber)
60. {
61. customers.remove(customer);
62. responseDelete=true;
63. break;
64. }
65. }
66. return responseDelete;
67. }
68. //finds the customer based on phoneNumber and updates the details of the same
69. public boolean updateCustomer(long phoneNumber, CustomerDTO customerDTO)
70. {
71. boolean responseUpdate=false;
72. for(CustomerDTO customer : customers)
73. {
74. if(customer.getPhoneNo() == phoneNumber)
75. {
76. if(customerDTO.getAddress()!=null)
77. {
78. customer.setAddress(customerDTO.getAddress());
79. }
80. if(customerDTO.getEmail()!=null)
81. {
82. customer.setEmail(customerDTO.getEmail());
83. }
84. responseUpdate = true;
85. break;
86. }
87. }
88. return responseUpdate;
89. }
90. }
1. package com.infytel.util;
2. /**
3. * The Enum ExceptionConstants.
4. */
5. public enum InfyTelConstants {
6. //Exception message constants
7. CUSTOMER_NOT_FOUND("customer.not.found"),
8. GENERAL_EXCEPTION_MESSAGE("general.exception"),
9. //Success message constants
10. CUSTOMER_UPDATE_SUCCESS("customer.update.success"),
11. CUSTOMER_DELETE_SUCCESS("customer.delete.success");
12. private final String type;
13. private InfyTelConstants(String type) {
14. this.type = type;
15. }
16. @Override
17. public String toString() {
18. return this.type;
19. }
20. }
4. import javax.validation.ConstraintViolationException;
5. import org.springframework.beans.factory.annotation.Autowired;
6. import org.springframework.core.env.Environment;
7. import org.springframework.http.HttpStatus;
8. import org.springframework.http.ResponseEntity;
9. import org.springframework.validation.ObjectError;
10. import org.springframework.web.bind.MethodArgumentNotValidException;
11. import org.springframework.web.bind.annotation.ExceptionHandler;
12. import org.springframework.web.bind.annotation.RestControllerAdvice;
13. import com.infytel.dto.ErrorMessage;
14. import com.infytel.util.InfyTelConstants;
15. @RestControllerAdvice
16. public class ExceptionControllerAdvice {
17. //this helps receiving the message/value related to the general exception from the
ValidationMessages.properties
18. @Autowired
19. private Environment environment;
20. //Handler for exceptions other than NoSuchCustomerException and validation exceptions
21. @ExceptionHandler(Exception.class)
22. public ResponseEntity<ErrorMessage> exceptionHandler2(Exception ex)
23. {
24. ErrorMessage error = new ErrorMessage();
25. error.setErrorCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
26. error.setMessage(environment.getProperty(InfyTelConstants.GENERAL_EXCEPTION_MESSA
GE.toString()));
27. return new ResponseEntity<>(error, HttpStatus.OK);
28. }
29. //Handler for NoSuchCustomerException
30. @ExceptionHandler(NoSuchCustomerException.class)
31. public ResponseEntity<ErrorMessage> exceptionHandler2(NoSuchCustomerException ex)
32. {
33. ErrorMessage error = new ErrorMessage();
34. error.setErrorCode(HttpStatus.BAD_REQUEST.value());
35. error.setMessage(ex.getMessage());
36. return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
37. }
38. //Handler that handles the exception raised because of invalid data that is received as method
argument (DTO)
39. @ExceptionHandler(MethodArgumentNotValidException.class)
40. public ResponseEntity<ErrorMessage>
handleValidationExceptions(MethodArgumentNotValidException ex)
41. {
Step 11: Deploy the appliaction by executing the class that contains the main method.
So, we have successfully created and deployed the REST endpoints with the provision for validating the
incoming data. Now, let us see how to test the same using Postman client.
Output Screenshots
Select POST from the drop-down and enter http://localhost:8080/infytel-7a/customers into the URL field.
Then, click on body to enter the following JSON data. Finally, click on Send. This action will create/add a
customer in Infytel.
1. {
2. "phoneNo":"9876543210",
3. "name":"Malar",
4. "password":"Abc3$",
5. "email":"[email protected]",
6. "age":"22",
7. "gender":"f",
8. "address":"Chennai",
9. "currentPlan":
10. {
11. "planId":"3"
12. }
13. }
The JSON data has to match the DTO object to which it is converted.
Step 3: Now, have a look at the CustomerDTO to know what can be the valid set of values for the fields
of the same and try sending an invalid value and see what happens. For example, let the password not be
in format.
Step 4 : Now, choose DELETE from the drop-down and enter http://localhost:8080/infytel-
7a/customers/9876543210 into the URL field where phone number is given as path-variable.
The phone number being passed in the above step was valid.
Step 5: Now, try with a phone number that is not in format and see what happens.
RestTemplate
There are situations where a Spring REST endpoint might be in need of contacting other RESTful
resources. Situations like this can be hadled with the help of REST client that is available in the Spring
framework. And, the name of this REST client is RestTemplate.
The following code snippet shows how to consumes a RESTful service exposed by url
:http://localhost:8080/infytel/customers using HTTP GET.
1. RestTemplate restTemplate = new RestTemplate();
2. String url="http://localhost:8080/infytel/customers";
3. List<CustomerDTO> customers = (List<CustomerDTO>) restTemplate.getForObject(url,
List.class);
4. for(CustomerDTO customer:customers)
5. {
6. System.out.println("Customer Name: "+customer.getName());
7. System.out.println("Customer Phone: "+customer.getPhoneNo());
8. System.out.println("Email Id: "+customer.getEmail());
9. }
Similarly, RESTful services of various other HTTP methods can be called using RestTemplate.
Objectives: To create a Spring REST endpoint and expose the same to the clients for consumption.
Scenario: An online telecom app called Infytel is exposing its telephone tariff plans as a RESTful service.
This is a simple RESTful endpoint which will be consumed by another application, Demo 8b.
Controller HTTP
method URI Remarks
Class Method
Will return the complete plan details for a
PlanController fetchPlanById() /plans/{planId} GET
given planId
Steps:
Step 1: Create a Maven project using Spring Initializer with web dependency and import the same in STS.
Step 2: Modify the imported project according to the following project structure:
Step 3: Look at the class InfytelDemo8AApplication under com.infytel package that gets created
automatically when the project is generated. We will modify this only when we need to go with custom
configuration.
1. package com.infytel;
2. import org.springframework.boot.SpringApplication;
3. import org.springframework.boot.autoconfigure.SpringBootApplication;
4. @SpringBootApplication
5. public class InfytelDemo8AApplication {
6. public static void main(String[] args) {
7. SpringApplication.run(InfytelDemo8AApplication.class, args);
8. }
9. }
12. @PostConstruct
13. public void populatePlans() {
14. plans = new ArrayList<>();
15. PlanDTO plan1 = new PlanDTO();
16. plan1.setPlanId(1);
17. plan1.setPlanName("Simple");
18. plan1.setLocalRate(3);
19. plan1.setNationalRate(5);
20. plans.add(plan1);
21. PlanDTO plan2 = new PlanDTO();
22. plan2.setPlanId(2);
23. plan2.setPlanName("Medium");
24. plan2.setLocalRate(5);
25. plan2.setNationalRate(8);
26. plans.add(plan2);
27. }
28. // methods fetchPlans() and plansLocalRate() go here
29. // fetching plan by id
30. public PlanDTO fetchPlanById(int planId) {
31. Optional<PlanDTO> optionalPlanDTO = plans.stream().filter(x -> x.getPlanId() ==
planId).findFirst();
32. return optionalPlanDTO.orElse(plans.get(0)); // if the optional contains a value, fine. Else the first
plan
33. // available in the list will be set
34. }
35. }
Step 9: Make sure that the project's pom.xml looks similar to the one shown below.
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
5. <modelVersion>4.0.0</modelVersion>
6. <parent>
7. <groupId>org.springframework.boot</groupId>
8. <artifactId>spring-boot-starter-parent</artifactId>
9. <version>2.1.4.RELEASE</version>
10. <relativePath /> <!-- lookup parent from repository -->
11. </parent>
12. <groupId>infytel</groupId>
13. <artifactId>infytel_demo8_a</artifactId>
14. <version>0.0.1-SNAPSHOT</version>
15. <name>infytel_demo8_a</name>
16. <description>REST client demo A</description>
17. <properties>
18. <java.version>1.8</java.version>
19. </properties>
20. <dependencies>
21. <dependency>
22. <groupId>org.springframework.boot</groupId>
23. <artifactId>spring-boot-starter-web</artifactId>
24. </dependency>
25. <dependency>
26. <groupId>org.springframework.boot</groupId>
27. <artifactId>spring-boot-starter-test</artifactId>
28. <scope>test</scope>
29. </dependency>
30. </dependencies>
31. <build>
32. <plugins>
33. <plugin>
34. <groupId>org.springframework.boot</groupId>
35. <artifactId>spring-boot-maven-plugin</artifactId>
36. </plugin>
37. </plugins>
38. </build>
39. </project>
Step 10: Deploy the application on the server by executing the class containing the main method.
So, we have successfully created and exposed a REST endpoint and made it available on port number
8080. Now, let us test the same using Postman client.
Output
Step 3: Testing this URL - http://localhost:8080/infytel-8a/plans/2 using HTTP GET to get the details
of the existing planId, 2.
Objectives: To create a Spring REST application which will consume a REST endpoint that is being
exposed by another REST application. We will learn,
How to consume a RESTful endpoint using RestTemplate.
Scenario: An online telecom app called Infytel wishes its functionalities to get exposed as RESTful
services. It has three controllers in total to deal with customer, plan and call details, among which, the
focus will be on the controller named CustomerController that consumes the plan details from an existing
endpoint of the application, infytel_demo_8a.
Following is the method that uses RestTemplate for the consumption of plan details.
Controller Class method URI HTTP method Remarks
CustomerController createCustomer() /customers POST To create a customer
Steps:
Step 1: Create a Maven project using Spring Initializer with web dependency and import the same in STS.
Step 2: Modify the imported project according to the following project structure:
Step 3: Look at the class InfytelDemo8BApplication under com.infytel package that gets created
automatically when the project is generated. We will modify this only when we need to go with custom
configuration.
1. package com.infytel;
2. import org.springframework.boot.SpringApplication;
3. import org.springframework.boot.autoconfigure.SpringBootApplication;
4. @SpringBootApplication
5. public class InfytelDemo8BApplication {
6. public static void main(String[] args) {
7. SpringApplication.run(InfytelDemo8BApplication.class, args);
8. }
9. }
8. import org.springframework.validation.Errors;
9. import org.springframework.validation.ObjectError;
10. import org.springframework.web.bind.annotation.DeleteMapping;
11. import org.springframework.web.bind.annotation.GetMapping;
12. import org.springframework.web.bind.annotation.PathVariable;
13. import org.springframework.web.bind.annotation.PostMapping;
14. import org.springframework.web.bind.annotation.PutMapping;
15. import org.springframework.web.bind.annotation.RequestBody;
16. import org.springframework.web.bind.annotation.RequestMapping;
17. import org.springframework.web.bind.annotation.RestController;
18. import org.springframework.web.client.RestTemplate;
19. import com.infytel.dto.CustomerDTO;
20. import com.infytel.dto.ErrorMessage;
21. import com.infytel.dto.PlanDTO;
22. import com.infytel.exceptions.NoSuchCustomerException;
23. import com.infytel.service.CustomerService;
24. @RestController
25. @RequestMapping("/customers")
26. public class CustomerController {
27. @Autowired
28. private CustomerService customerService;
29. // Fetching customer details
30. @GetMapping(produces = "application/json")
31. public List<CustomerDTO> fetchCustomer() {
32. return customerService.fetchCustomer();
33. }
34. // Adding a customer
35. @PostMapping(consumes = "application/json")
36. public ResponseEntity createCustomer(@Valid @RequestBody CustomerDTO customerDTO,
Errors errors) {
37. String response = "";
38. if (errors.hasErrors()) {
39. response = errors.getAllErrors().stream().map(ObjectError::getDefaultMessage)
40. .collect(Collectors.joining(","));
41. ErrorMessage error = new ErrorMessage();
42. error.setErrorCode(HttpStatus.NOT_ACCEPTABLE.value());
43. error.setMessage(response);
44. return ResponseEntity.ok(error);
45. } else {
46. PlanDTO planDTOReceived = new RestTemplate().getForObject(
47. "http://localhost:8080/infytel-8a/plans/" + customerDTO.getCurrentPlan().getPlanId(),
PlanDTO.class);
48. customerDTO.setCurrentPlan(planDTOReceived);
49. response = customerService.createCustomer(customerDTO);
50. return ResponseEntity.ok(response);
51. }
52. }
53. // Updating an existing customer
54. @PutMapping(value = "/{phoneNumber}", consumes = "application/json")
55. public String updateCustomer(@PathVariable("phoneNumber") long phoneNumber,
@RequestBody CustomerDTO customerDTO) {
56. return customerService.updateCustomer(phoneNumber, customerDTO);
57. }
58. // Deleting a customer
59. @DeleteMapping(value = "/{phoneNumber}", produces = "text/html")
60. public String deleteCustomer(@PathVariable("phoneNumber") long phoneNumber) throws
NoSuchCustomerException {
61. return customerService.deleteCustomer(phoneNumber);
62. }
63. }
1. package com.infytel.dto;
2. public class ErrorMessage {
3. private int errorCode;
4. private String message;
5. public int getErrorCode() {
6. return errorCode;
7. }
8. public void setErrorCode(int errorCode) {
9. this.errorCode = errorCode;
10. }
11. public String getMessage() {
12. return message;
13. }
14. public void setMessage(String message) {
15. this.message = message;
16. }
17. }
1. package com.infytel.dto;
2. public class FriendFamilyDTO {
3. long phoneNo;
4. long friendAndFamily;
5. public long getPhoneNo() {
6. return phoneNo;
7. }
8. public void setPhoneNo(long phoneNo) {
9. this.phoneNo = phoneNo;
10. }
1. package com.infytel.dto;
2. import javax.xml.bind.annotation.XmlRootElement;
3. @XmlRootElement
4. public class PlanDTO {
5. Integer planId;
6. String planName;
7. Integer nationalRate;
8. Integer localRate;
9. public Integer getPlanId() {
10. return planId;
11. }
12. public void setPlanId(Integer planId) {
13. this.planId = planId;
14. }
15. public String getPlanName() {
16. return planName;
17. }
18. public void setPlanName(String planName) {
19. this.planName = planName;
20. }
21. public Integer getNationalRate() {
1. package com.infytel.exceptions;
2. public class NoSuchCustomerException extends Exception {
3. private static final long serialVersionUID = 1L;
4. public NoSuchCustomerException() {
5. super();
6. }
7. public NoSuchCustomerException(String errors) {
8. super(errors);
9. }
10. }
1. server.port=8090
2. server.servlet.context-path=/infytel-8b
Step 10: Make sure that the project's pom.xml looks similar to the one that is shown below.
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
5. <modelVersion>4.0.0</modelVersion>
6. <parent>
7. <groupId>org.springframework.boot</groupId>
8. <artifactId>spring-boot-starter-parent</artifactId>
9. <version>2.1.4.RELEASE</version>
10. <relativePath /> <!-- lookup parent from repository -->
11. </parent>
12. <groupId>infytel</groupId>
13. <artifactId>infytel_demo8_b</artifactId>
14. <version>0.0.1-SNAPSHOT</version>
15. <name>infytel_demo8_b</name>
16. <description>REST client demo B</description>
17. <properties>
18. <java.version>1.8</java.version>
19. </properties>
20. <dependencies>
21. <dependency>
22. <groupId>org.springframework.boot</groupId>
23. <artifactId>spring-boot-starter-web</artifactId>
24. </dependency>
25. <dependency>
26. <groupId>org.springframework.boot</groupId>
27. <artifactId>spring-boot-starter-test</artifactId>
28. <scope>test</scope>
29. </dependency>
30. </dependencies>
31. <build>
32. <plugins>
33. <plugin>
34. <groupId>org.springframework.boot</groupId>
35. <artifactId>spring-boot-maven-plugin</artifactId>
36. </plugin>
37. </plugins>
38. </build>
39. </project>
Step 11: Deploy the application on the server by executing the class containing the main method.
So, we have successfully created a REST client that consumes the REST endpoint that exposes the plan
details.
Now, let us see how can we test the same using Postman client.
Output
Testing Web Service using Postman
Step 1: Launch Postman.
Step 2: Test this URL - http://localhost:8090/infytel-8b/customers using HTTP GET to check the
existing customers in infytel.
Step 3: Adding a New Customer Using http://localhost:8090/infytel-8b/customers and the sample data is
as given below
1. {
2. "phoneNo": 8123456789,
3. "name": "John",
4. "email": "[email protected]",
5. "age": 25,
6. "gender": "m",
7. "friendAndFamily": [
8. {
9. "phoneNo": 8123456789,
10. "friendAndFamily": 86525232312
11. },
12. {
13. "phoneNo": 8123456789,
14. "friendAndFamily": 9876543210
15. }
16. ],
17. "password": "Stark@987",
18. "address": "Pune",
19. "currentPlan": {
20. "planId": 1
21. }
22. }
You will get the following response.
Step 4: Verify if the new customer is added and also check the complete plan details chosen by the
customers appears or not.
Observe that in the customer profile, the complete plan details are appearing as the customer REST
resource consumed the plan REST resource to populate the customer details while creating a new
Customer.
Versioning a service becomes important when we want to create a new version of an already existing
service. But at the same time, we need to support the earlier version as well, to ensure backward
compatibility.
Example: There is a need for two versions of functionality that fetches the plan details of Infytel telecom
application. And, this fetch operation is based on the planId being passed as a path variable.
version-1: Fetches the complete plan details that include plainId, planName, localRate and nationalRate
version-2: Fetches only the localRate and nationalRate.
Below is the URI proposed to be used
1. GET http://localhost:8080/infytel/plans/v1/{planId}
2. eg:http://localhost:8080/infytel/plans/v1/1
3. GET http://localhost:8080/infytel/plans/v2/{planId}
4. eg:http://localhost:8080/infytel/plans/v2/1
For Version-1
1. @GetMapping(value = "v1/{planId}")
2. public ResponseEntity<PlanDTO> getPlan(@PathVariable("planId")String planId)
3. {
4. //Return the complete plan details
5. }
For Version-2
1. @GetMapping(value = "v2/{planId}")
2. public ResponseEntity<PlanDTO> getPlanv2(@PathVariable("planId")String planId)
3. {
4. //Returns only the localRate and nationalRate
5. }
Observe, how a functionality with two different versions or approaches is made available in a REST
resource. Also, focus on how the consumers call the different versions of the API method using the URIs
that carry information about versioning.
This type of versioning requires the inclusion of custom headers in the request URI to map to the
correct version of the API endpoint.
Let us take the service that fetches plan details, here as well.
For Version-1
1. @GetMapping(value = "{planId}",headers = "X-API-VERSION=1")
2. public ResponseEntity<PlanDTO> getPlan(@PathVariable("planId")String planId)
3. {
4. //Return the complete plan details
5. }
For Version-2
1. @GetMapping(value = "{planId}",headers = "X-API-VERSION=2")
2. public ResponseEntity<PlanDTO> getPlanv2(@PathVariable("planId")String planId)
3. {
4. //Returns only the localRate and nationalRate
5. }
Observe, how a functionality with two different versions or approaches is made available in a REST
resource. Also, focus on how the consumers call the different versions of the API method using the URIs
that carry information about versioning as part of the request headers.
Accept Header Versioning approach is one other way of versioning the API methods that insists the usage
of Accept Header in the request.
1. GET http://localhost:8080/infytel/plans/{planId}
2. headers=[Accept=application/vnd.plans.app-v1+json]
3. eg:http://localhost:8080/infytel/plans/1 and pass the header information
headers=[Accept=application/vnd.plans.app-v1+json]
4. GET http://localhost:8080/infytel/plans/{planId}
5. headers=[Accept=application/vnd.plans.app-v2+json]
6. eg:http://localhost:8080/infytel/plans/1 and pass the header information
headers=[Accept=application/vnd.plans.app-v2+json]
For Version-1
1. @GetMapping(value = "/{planId}", produces = "application/vnd.plans.app-v1+json")
2. public ResponseEntity<PlanDTO> getPlan(@PathVariable("planId")String planId)
3. {
4. //Return the complete plan details
5. }
For Version-2
1. @GetMapping(value = "/{planId}", produces = "application/vnd.plans.app-v2+json")
2. public ResponseEntity<PlanDTO> getPlanv2(@PathVariable("planId")String planId)
3. {
4. //Returns only the localRate and nationalRate
5. }
Observe, how a functionality with two different versions or approaches is made available in a REST
resource. Also, focus on how the consumers call the different versions of the API method using the URIs
that carry information about versioning as part of the request headers named, accept. One more important
thing to be noted here is the inclusion of custom MIME type.
Request parameter versioning can be achieved by providing the request parameter in the URI that holds
the version details.
1. GET http://localhost:8080/infytel/plans/{planId}?version=
2. eg:http://localhost:8080/infytel/plans/1?version=1
3. GET http://localhost:8080/infytel/plans/{planId}?version=
4. eg:http://localhost:8080/infytel/plans/1?version=2
For Version-1
1. @GetMapping(value = "/{planId}", params = "version=1")
2. public ResponseEntity<PlanDTO> getPlan(@PathVariable("planId")String planId)
3. {
4. //Return the complete plan details
5. }
For Version-2
6. @GetMapping(value = "/{planId}", params = "version=2")
7. public ResponseEntity<PlanDTO> getPlanv2(@PathVariable("planId")String planId)
8. {
9. //Returns only the localRate and nationalRate
10. }
Observe, how a functionality with two different versions or approaches is made available in a REST
resource. Also, focus on how the consumers call the different versions of the API method using the URIs
that carry information about versioning in the form of request parameter.
Objectives: To create a Spring REST API with multiple versions of the same functionality. We will learn,
To create different versions of a REST method which can be targetted with the same URI that
holds, request parameter to differentiate among the versions.
Scenario:An online telecom app called Infytel wishes its functionalities to get exposed as RESTful
services. It has three controllers in total to deal with customer, plan and call details, among which, the
focus will be on the controller named
PlanDetailsController.
Notice that the URI is the same for both the versions, except the value that will be mentioned for the
request parameter.
Steps:
Step 1: Create a Maven project using Spring Initializer with web dependency and import the same in STS.
Step 2: Modify the imported project according to the following project structure:
Step 3: Look at the class InfytelDemo11Application under com.infytel package that gets created
automatically when the project is generated. We will modify this only when we need to go with custom
configuration.
1. package com.infytel;
2. import org.springframework.boot.SpringApplication;
3. import org.springframework.boot.autoconfigure.SpringBootApplication;
4. @SpringBootApplication
5. public class InfytelDemo11Application {
6. public static void main(String[] args) {
7. SpringApplication.run(InfytelDemo11Application.class, args);
8. }
9. }
4. import org.springframework.stereotype.Service;
5. import org.springframework.web.bind.annotation.PathVariable;
6. import com.infytel.dto.PlanDTO;
7. import com.infytel.repository.PlanRepository;
8. @Service
9. public class PlanService {
10. @Autowired
11. private PlanRepository planRepository;
12. // methods fetchPlans() and plansLocalRate() go here
13. public PlanDTO fetchPlanById(int planId) {
14. return planRepository.fetchPlanById(planId);
15. }
16. public Map<String, Integer> fetchPlanByIdv2(@PathVariable("planId") int planId) {
17. return planRepository.fetchPlanByIdv2(planId);
18. }
19. }
Step 9: Make sure that the project's pom.xml looks similar to the one shown below.
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
5. <modelVersion>4.0.0</modelVersion>
6. <parent>
7. <groupId>org.springframework.boot</groupId>
8. <artifactId>spring-boot-starter-parent</artifactId>
9. <version>2.1.4.RELEASE</version>
10. <relativePath /> <!-- lookup parent from repository -->
11. </parent>
12. <groupId>infytel</groupId>
13. <artifactId>infytel_demo11</artifactId>
14. <version>0.0.1-SNAPSHOT</version>
15. <name>infytel_demo11</name>
16. <description>Versioning Spring REST API</description>
17. <properties>
18. <java.version>1.8</java.version>
19. </properties>
20. <dependencies>
21. <dependency>
22. <groupId>org.springframework.boot</groupId>
23. <artifactId>spring-boot-starter-web</artifactId>
24. </dependency>
25. <dependency>
26. <groupId>org.springframework.boot</groupId>
27. <artifactId>spring-boot-starter-test</artifactId>
28. <scope>test</scope>
29. </dependency>
30. </dependencies>
31. <build>
32. <plugins>
33. <plugin>
34. <groupId>org.springframework.boot</groupId>
35. <artifactId>spring-boot-maven-plugin</artifactId>
36. </plugin>
37. </plugins>
38. </build>
39. </project>
Step 10: Deploy the application on the server by executing the class containing the main method.
So, we have successfully created and deployed the REST endpoints. Now, let us see how the same can be
tested using any standard web browser.
Output
Step 3: Test this URL - http://localhost:8080/infytel-11/plans/2?version=2. Only the local and national
rates of plan details will be rendered as output.
Why CORS?
Typically, scripts running in a web application of one origin (domain) cannot have access to the resources
of a server available at a different origin. If the clients of different origins try to access the REST
endpoints of an application, CORS error will be thrown simply. And, below is a sample of the same.
1. Cross-Origin Request Blocked: The Same Origin Policy disallows
2. reading the remote resource at https://domain-1.com
Look at the image below, which depicts the request for resources by the clients of the same origin and
different origins as well.
Request for resources from a different domain will encounter CORS error.
To overcome this error, we need to enable cross-origin requests at the server-side so that the browsers
which run the client code can allow scripts to make a Cross-origin call.
Cross-origin calls can be enabled for Spring REST resources in three ways by applying:
CORS configuration on the REST controller methods
CORS configuration on the REST controller itself
CORS configuration globally (common to the entire application)
Here, the annotation, @CrossOrigin enables cross-origin requests only for the specific handler method.
By default, it allows all origins, all headers, all HTTP methods specified in the @RequestMapping
annotation. Also, a lot more other defaults are available.
We can customize this behavior by specifying the value for the following attributes that are available
with Http method mapping annotations (@GetMapping, @PostMapping, etc.,)
origins - list of allowed origins to access the method
Example:
1. @Controller
2. @RequestMapping(path="/customers")
3. public class CustomerController
4. {
5. @CrossOrigin(origins = "*", allowedHeaders = "*")
6. @GetMapping()
7. public String homeInit(Model model) {
8. return "home";
9. }
10. }
Look at the code below. The starter class has been modified to implement WebMvcConfigurer and
override addCorsMappings() method that takes the CorsRegistry object as argument using which we can
configure the allowed set of domains and HTTP methods as well.
1. @SpringBootApplication
2. public class InfytelApplication implements WebMvcConfigurer {
3. public static void main(String[] args) {
4. SpringApplication.run(InfytelDemo8BApplication.class, args);
5. }
6. @Override
7. public void addCorsMappings(CorsRegistry registry) {
8. registry.addMapping("/**").allowedMethods("GET", "POST");
9. }
10. }
Scenario: An online telecom app called Infytel is exposing its "customer management profile" as a
RESTful resource that provides provisions to create, fetch, delete and update customer details.
HTTP
Controller Class methods URI Remarks
Method
To fetch all the Infytel
CustomerController fetchCustomer() /customers GET
customers
To create a new customer
CustomerController createCustomer() /customers POST
in Infytel
To delete an existing
customer of Infytel
CustomerController deleteCustomer() /customers/{phoneNumber} DELETE
identified by
phoneNumber
To update an existing
customer of
CustomerController updateCustomer() /customers/{phoneNumber} PUT
Infytel identified by
phoneNumber
Steps:
Step 1: Create a Maven project using Spring Initializer with web dependency and import the same in STS.
Step 2: Modify the imported project according to the following project structure:
Step 3: Look at the class InfytelDemo12AApplication under com.infytel package that gets created
automatically when the project is generated. We will modify this only when we need to go with custom
configuration. And, CORS configuration is included here.
1. package com.infytel;
2. import org.springframework.boot.SpringApplication;
3. import org.springframework.boot.autoconfigure.SpringBootApplication;
4. import org.springframework.web.servlet.config.annotation.CorsRegistry;
5. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
6. @SpringBootApplication
7. public class InfytelDemo12AApplication implements WebMvcConfigurer {
8. public static void main(String[] args) {
9. SpringApplication.run(InfytelDemo12AApplication.class, args);
10. }
11. //Code for CORS configuration
12. @Override
13. public void addCorsMappings(CorsRegistry registry) {
14. registry.addMapping("/**").allowedMethods("GET", "POST");
15. }
16. }
71. return "CustomerDTO [phoneNo=" + phoneNo + ", name=" + name + ", age=" + age + ", gender="
+ gender + ", friendAndFamily=" + friendAndFamily + ", password=" + password + ", address=" +
address + "]";
72. }
73. }
1. package com.infytel.dto;
2. public class ErrorMessage {
3. private int errorCode;
4. private String message;
5. public int getErrorCode() {
6. return errorCode;
7. }
8. public void setErrorCode(int errorCode) {
9. this.errorCode = errorCode;
10. }
11. public String getMessage() {
12. return message;
13. }
14. public void setMessage(String message) {
15. this.message = message;
16. }
17. }
1. package com.infytel.dto;
2. public class FriendFamilyDTO {
3. long phoneNo;
4. long friendAndFamily;
5. public long getPhoneNo() {
6. return phoneNo;
7. }
8. public void setPhoneNo(long phoneNo) {
9. this.phoneNo = phoneNo;
10. }
11. public long getFriendAndFamily() {
12. return friendAndFamily;
13. }
14. public void setFriendAndFamily(long friendAndFamily) {
15. this.friendAndFamily = friendAndFamily;
16. }
17. public FriendFamilyDTO(long phoneNo, long friendAndFamily) {
18. this();
19. this.phoneNo = phoneNo;
1. package com.infytel.dto;
2. import javax.xml.bind.annotation.XmlRootElement;
3. @XmlRootElement
4. public class PlanDTO {
5. Integer planId;
6. String planName;
7. Integer nationalRate;
8. Integer localRate;
9. public Integer getPlanId() {
10. return planId;
11. }
12. public void setPlanId(Integer planId) {
13. this.planId = planId;
14. }
15. public String getPlanName() {
16. return planName;
17. }
18. public void setPlanName(String planName) {
19. this.planName = planName;
20. }
21. public Integer getNationalRate() {
22. return nationalRate;
23. }
24. public void setNationalRate(Integer nationalRate) {
25. this.nationalRate = nationalRate;
26. }
27. public Integer getLocalRate() {
28. return localRate;
29. }
30. public void setLocalRate(Integer localRate) {
1. package com.infytel.exceptions;
2. public class NoSuchCustomerException extends Exception {
3. private static final long serialVersionUID = 1L;
4. public NoSuchCustomerException() {
5. super();
6. }
7. public NoSuchCustomerException(String errors) {
8. super(errors);
9. }
10. }
79. }
80. }
Step 10: Make sure that the project's pom.xml looks similar to the one shown here.
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
5. <modelVersion>4.0.0</modelVersion>
6. <parent>
7. <groupId>org.springframework.boot</groupId>
8. <artifactId>spring-boot-starter-parent</artifactId>
9. <version>2.1.4.RELEASE</version>
10. <relativePath /> <!-- lookup parent from repository -->
11. </parent>
12. <groupId>com.infytel</groupId>
13. <artifactId>infytel_demo12_a</artifactId>
14. <version>0.0.1-SNAPSHOT</version>
15. <name>infytel_demo12_a</name>
16. <description>Demo project for Spring Boot</description>
17. <properties>
18. <java.version>1.8</java.version>
19. </properties>
20. <dependencies>
21. <dependency>
22. <groupId>org.springframework.boot</groupId>
23. <artifactId>spring-boot-starter-web</artifactId>
24. </dependency>
25. <dependency>
26. <groupId>org.springframework.boot</groupId>
27. <artifactId>spring-boot-starter-test</artifactId>
28. <scope>test</scope>
29. </dependency>
30. </dependencies>
31. <build>
32. <plugins>
33. <plugin>
34. <groupId>org.springframework.boot</groupId>
35. <artifactId>spring-boot-maven-plugin</artifactId>
36. </plugin>
37. </plugins>
38. </build>
39. </project>
Step 11: Deploy the application on the server by executing the class containing the main method.
So, we have successfully created and deployed the REST endpoints. Now, let us see how can we test the
same using a browser.
Output
Objectives: A Simple UI application to test the CORS enabled Spring RESTful endpoint.
Scenario: This is a simple UI application developed using Javascript and JSP to test if we are able to
access the REST endpoints that are enabled with CORS. Observe the AJAX call that is placed from the .js
file.
Steps:
Step 1: Create a Maven web project.
Step 2: Modify the project according to the following project structure:
5. <script
6. src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
7. <script src="Customer.js"></script>
8. </head>
9. <body>
10. <Center>
11. <h1>Infytel Customer List</h1>
12. </Center>
13. <div>
14. <center>
15. <div class="customer-id"></div>
16. </center>
17. </div>
18. </body>
19. </html>
Step 6: Make sure that the project's pom.xml looks similar to the one shown here.
1. <project xmlns="http://maven.apache.org/POM/4.0.0"
2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-
v4_0_0.xsd">
4. <modelVersion>4.0.0</modelVersion>
5. <groupId>com.infytel</groupId>
6. <artifactId>infytel_demo12_b</artifactId>
7. <packaging>war</packaging>
8. <version>0.0.1-SNAPSHOT</version>
9. <name>infytel_demo12_b</name>
10. <url>http://maven.apache.org</url>
11. <properties>
12. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
13. <maven.compiler.source>1.8</maven.compiler.source>
14. <maven.compiler.target>1.8</maven.compiler.target>
15. </properties>
16. <dependencies>
17. <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
18. <dependency>
19. <groupId>javax.servlet</groupId>
20. <artifactId>javax.servlet-api</artifactId>
21. <version>4.0.1</version>
22. <scope>provided</scope>
23. </dependency>
24. </dependencies>
25. <build>
26. <finalName>infytel_demo12_b</finalName>
27. <pluginManagement><!-- lock down plugins versions to avoid using Maven
28. defaults (may be moved to parent pom) -->
29. <plugins>
30. <plugin>
31. <artifactId>maven-clean-plugin</artifactId>
32. <version>3.1.0</version>
33. </plugin>
34. <!-- see http://maven.apache.org/ref/current/maven-core/default-
bindings.html#Plugin_bindings_for_war_packaging -->
35. <plugin>
36. <artifactId>maven-resources-plugin</artifactId>
37. <version>3.0.2</version>
38. </plugin>
39. <plugin>
40. <artifactId>maven-compiler-plugin</artifactId>
41. <version>3.8.0</version>
42. </plugin>
43. <plugin>
44. <artifactId>maven-surefire-plugin</artifactId>
45. <version>2.22.1</version>
46. </plugin>
47. <plugin>
48. <artifactId>maven-war-plugin</artifactId>
49. <version>3.2.2</version>
50. </plugin>
51. <plugin>
52. <artifactId>maven-install-plugin</artifactId>
53. <version>2.5.2</version>
54. </plugin>
55. <plugin>
56. <artifactId>maven-deploy-plugin</artifactId>
57. <version>2.8.2</version>
58. </plugin>
59. <plugin>
60. <groupId>org.apache.tomcat.maven</groupId>
61. <artifactId>tomcat7-maven-plugin</artifactId>
62. <version>2.2</version>
63. <configuration>
64. </configuration>
65. </plugin>
66. </plugins>
67. </pluginManagement>
68. </build>
69. </project>
Step 7: Execute the applications 12a and 12b and make them available in different port numbers, say 8090
and 8080.
Output
Step 1: Right click on the project. Choose Run As->Maven build... option. Specify the goal, tomcat7:run.
Click on Apply and Run.
Step 3: Before doing so, just disable CORS config that is there available in the starter code of demo
12a. Enter this URL http://localhost:8080/infytel_demo12_b/Customer.jsp. Following will be the
response.
Now Press ctrl+shift+J. The reason behind this failure is the lack of CORS support.
Now, enable CORS config that is there available in the starter code of demo 12a and follow the next step
to get the desired output.
Step
5: Now try posting another customer object with postman client.
From the drop-down, select POST and enter http://localhost:8090/infytel-12a/customers into the
URL field to test the service. And, click on the body to enter the following JSON data. The below data is
used to create a new customer in infytel.
The JSON data has to exactly match the DTO object, to which it is converted.
1. {
2. "phoneNo": 8123456789,
3. "name": "Stark",
4. "email": "[email protected]",
5. "age": 21,
6. "gender": "m",
7. "friendAndFamily": [
8. {
9. "phoneNo": 8123456789,
10. "friendAndFamily": 86525232312
11. },
12. {
13. "phoneNo": 8123456789,
14. "friendAndFamily": 9876543210
15. }
16. ],
17. "password": "Stark@987",
18. "address": "Pune",
19. "currentPlan": {
20. "planId": 1,
21. "planName": "Simple",
22. "nationalRate": 5,
23. "localRate": 3
24. }
25. }
Note: Observe the CustomerDTO's variable names. It is important that the variable name in
CustomerDTO and JSON key has to match exactly, for the correct mapping to happen.
Securing REST APIs here, follows the same steps involved in securing any other web application that is
developed using Spring Boot. As we all know, Spring Boot minimizes configuration and that goes good
with security as well.
Basic Authentication:
When client requests for a protected resource, server demands the client to supply the authentication
credentials.
Client will get the requested resource only when the credentials pass authentication at server side.
With Basic Authentication in place, clients send their Base64 encoded credentials with each request using
HTTP Authorization header.
This type of authentication does not mandate us to have any Login page which is the very standard way of
authenticating the client requests in any web application. In addition, there is no need to store the
credentials or any related info in the session.
Each request here, is treated as an independent request which in turn promotes scalability.
Enabling security in a Spring Boot application needs the inclusion of the following dependency as the
very first step.
1. <dependency>
2. <groupId>org.springframework.boot</groupId>
3. <artifactId>spring-boot-starter-security</artifactId>
4. </dependency>
This will include the SecurityAutoConfiguration class – containing the initial or default security
configuration.
There are some predefined properties as shown below which needs to be configured in the
application.properties file.
spring.security.user.name
spring.security.user.password
Following steps are required only when we need to customize the security related aspects.
1. @Configuration
2. @EnableWebSecurity
3. public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
4. }
Objectives: To enable basic authentication for a Spring REST API using Spring Security
Scenario: An online telecom app called Infytel is exposing its customer management profile as a RESTful
resource. The customer resource titled, CustomerController allows the clients to create, fetch, delete
and update the customer details. Following are the HTTP operation that will come to our focus, here.
HTTP
Controller Class method name URI Remarks
Method
CustomerController fetchCustomer() /customers GET Fetch all the customers
CustomerController createCustomer() /customers POST Create a new Customer
HTTP
Controller Class method name URI Remarks
Method
Delete an existing
CustomerController deleteCustomer() /customers/{phoneNumber} Delete Customer identified by
phoneNumber
Update an existing
CustomerController updateCustomer() /customers/{phoneNumber} PUT Customer identified by
phoneNumber
Step 3: Look at the class InfytelDemo9Application under com.infytel package that gets created
automatically when the project is generated. We will modify this only when we need to go with custom
configuration.
1. package com.infytel;
2. import org.springframework.boot.SpringApplication;
3. import org.springframework.boot.autoconfigure.SpringBootApplication;
4. @SpringBootApplication
5. public class InfytelDemo9Application {
6. public static void main(String[] args) {
7. SpringApplication.run(InfytelDemo9Application.class, args);
8. }
9. }
1. package com.infytel.config;
2. import org.springframework.beans.factory.annotation.Autowired;
3. import org.springframework.context.annotation.Bean;
4. import org.springframework.context.annotation.Configuration;
5. import
org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBui
lder;
6. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
7. import
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapte
r;
8. import org.springframework.security.config.http.SessionCreationPolicy;
9. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
10. import org.springframework.security.crypto.password.PasswordEncoder;
11. @Configuration
12. public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
13. @Autowired
14. public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
15. auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("infytel"
))
16. .authorities("ROLE_ADMIN");
17. }
18. @Override
19. protected void configure(HttpSecurity http) throws Exception {
20. http.authorizeRequests().antMatchers("/").permitAll().anyRequest().authenticated().and().httpBasi
c().and()
21. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER).and().csrf().disable(
);
22. }
23. @Bean
24. public PasswordEncoder passwordEncoder() {
25. return new BCryptPasswordEncoder();
26. }
27. }
7. import org.springframework.http.ResponseEntity;
8. import org.springframework.validation.Errors;
9. import org.springframework.validation.ObjectError;
10. import org.springframework.web.bind.annotation.DeleteMapping;
11. import org.springframework.web.bind.annotation.GetMapping;
12. import org.springframework.web.bind.annotation.PathVariable;
13. import org.springframework.web.bind.annotation.PostMapping;
14. import org.springframework.web.bind.annotation.PutMapping;
15. import org.springframework.web.bind.annotation.RequestBody;
16. import org.springframework.web.bind.annotation.RequestMapping;
17. import org.springframework.web.bind.annotation.RestController;
18. import org.springframework.web.client.RestTemplate;
19. import com.infytel.dto.CustomerDTO;
20. import com.infytel.dto.ErrorMessage;
21. import com.infytel.dto.PlanDTO;
22. import com.infytel.exceptions.NoSuchCustomerException;
23. import com.infytel.service.CustomerService;
24. @RestController
25. @RequestMapping("/customers")
26. public class CustomerController {
27. @Autowired
28. private CustomerService customerService;
29. // Fetching customer details
30. @GetMapping(produces = "application/json")
31. public List<CustomerDTO> fetchCustomer() {
32. return customerService.fetchCustomer();
33. }
34. // Adding a customer
35. @PostMapping(consumes = "application/json")
36. public ResponseEntity createCustomer(@Valid @RequestBody CustomerDTO customerDTO,
Errors errors) {
37. String response = "";
38. if (errors.hasErrors()) {
39. response = errors.getAllErrors().stream().map(ObjectError::getDefaultMessage)
40. .collect(Collectors.joining(","));
41. ErrorMessage error = new ErrorMessage();
42. error.setErrorCode(HttpStatus.NOT_ACCEPTABLE.value());
43. error.setMessage(response);
44. return ResponseEntity.ok(error);
45. } else {
46. PlanDTO planDTOReceived = new RestTemplate().getForObject(
47. "http://localhost:8080/plans/" + customerDTO.getCurrentPlan().getPlanId(), PlanDTO.class);
48. customerDTO.setCurrentPlan(planDTOReceived);
49. response = customerService.createCustomer(customerDTO);
50. return ResponseEntity.ok(response);
51. }
52. }
53. // Updating an existing customer
54. @PutMapping(value = "/{phoneNumber}", consumes = "application/json")
55. public String updateCustomer(@PathVariable("phoneNumber") long phoneNumber,
@RequestBody CustomerDTO customerDTO) {
56. return customerService.updateCustomer(phoneNumber, customerDTO);
57. }
58. // Deleting a customer
59. @DeleteMapping(value = "/{phoneNumber}", produces = "text/html")
60. public String deleteCustomer(@PathVariable("phoneNumber") long phoneNumber) throws
NoSuchCustomerException {
61. return customerService.deleteCustomer(phoneNumber);
62. }
63. }
23. }
24. public void setCurrentPlan(PlanDTO currentPlan) {
25. this.currentPlan = currentPlan;
26. }
27. public String getPassword() {
28. return password;
29. }
30. public void setPassword(String password) {
31. this.password = password;
32. }
33. public String getAddress() {
34. return address;
35. }
36. public void setAddress(String address) {
37. this.address = address;
38. }
39. public List<FriendFamilyDTO> getFriendAndFamily() {
40. return friendAndFamily;
41. }
42. public void setFriendAndFamily(List<FriendFamilyDTO> friendAndFamily) {
43. this.friendAndFamily = friendAndFamily;
44. }
45. public long getPhoneNo() {
46. return phoneNo;
47. }
48. public void setPhoneNo(long phoneNo) {
49. this.phoneNo = phoneNo;
50. }
51. public String getName() {
52. return name;
53. }
54. public void setName(String name) {
55. this.name = name;
56. }
57. public int getAge() {
58. return age;
59. }
60. public void setAge(int age) {
61. this.age = age;
62. }
63. public char getGender() {
64. return gender;
65. }
66. public void setGender(char gender) {
67. this.gender = gender;
68. }
69. @Override
70. public String toString() {
71. return "CustomerDTO [phoneNo=" + phoneNo + ", name=" + name + ", age=" + age + ", gender="
+ gender + ", friendAndFamily=" + friendAndFamily + ", password=" + password + ", address=" +
address + "]";
72. }
73. }
1. package com.infytel.dto;
2. public class ErrorMessage {
3. private int errorCode;
4. private String message;
5. public int getErrorCode() {
6. return errorCode;
7. }
8. public void setErrorCode(int errorCode) {
9. this.errorCode = errorCode;
10. }
11. public String getMessage() {
12. return message;
13. }
14. public void setMessage(String message) {
15. this.message = message;
16. }
17. }
1. package com.infytel.dto;
2. public class FriendFamilyDTO {
3. long phoneNo;
4. long friendAndFamily;
5. public long getPhoneNo() {
6. return phoneNo;
7. }
8. public void setPhoneNo(long phoneNo) {
9. this.phoneNo = phoneNo;
10. }
11. public long getFriendAndFamily() {
12. return friendAndFamily;
13. }
1. package com.infytel.dto;
2. import javax.xml.bind.annotation.XmlRootElement;
3. @XmlRootElement
4. public class PlanDTO {
5. Integer planId;
6. String planName;
7. Integer nationalRate;
8. Integer localRate;
9. public Integer getPlanId() {
10. return planId;
11. }
12. public void setPlanId(Integer planId) {
13. this.planId = planId;
14. }
15. public String getPlanName() {
16. return planName;
17. }
18. public void setPlanName(String planName) {
19. this.planName = planName;
20. }
21. public Integer getNationalRate() {
22. return nationalRate;
23. }
24. public void setNationalRate(Integer nationalRate) {
1. package com.infytel.exceptions;
33. customerDTO.setFriendAndFamily(friendAndFamily);
34. customers = new ArrayList<>();
35. customers.add(customerDTO);
36. }
37. // creates customer
38. public String createCustomer(CustomerDTO customerDTO) {
39. customers.add(customerDTO);
40. return "Customer with" + customerDTO.getPhoneNo() + "added successfully";
41. }
42. // fetches customer
43. public List<CustomerDTO> fetchCustomer() {
44. return customers;
45. }
46. // deletes customer - exception handling incorporated
47. public String deleteCustomer(long phoneNumber) throws NoSuchCustomerException {
48. boolean notfound = true;
49. String response = "Customer of:" + phoneNumber + "\t does not exist";
50. for (CustomerDTO customer : customers) {
51. if (customer.getPhoneNo() == phoneNumber) {
52. customers.remove(customer);
53. response = customer.getName() + " with phoneNumber " + customer.getPhoneNo() + " deleted
successfully";
54. notfound = false;
55. break;
56. }
57. }
58. if (notfound)
59. throw new NoSuchCustomerException("Customer does not exist :" + phoneNumber);
60. return response;
61. }
62. // updates customer
63. public String updateCustomer(long phoneNumber, CustomerDTO customerDTO) {
64. String response = "Customer of:" + phoneNumber + "\t does not exist";
65. for (CustomerDTO customer : customers) {
66. if (customer.getPhoneNo() == phoneNumber) {
67. if (customerDTO.getName() != null)
68. customer.setName(customerDTO.getName());
69. if (customerDTO.getAddress() != null)
70. customer.setAddress(customerDTO.getAddress());
71. if (customerDTO.getPassword() != null)
72. customer.setPassword(customerDTO.getPassword());
73. customers.set(customers.indexOf(customer), customer);
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
5. <modelVersion>4.0.0</modelVersion>
6. <parent>
7. <groupId>org.springframework.boot</groupId>
8. <artifactId>spring-boot-starter-parent</artifactId>
9. <version>2.1.4.RELEASE</version>
10. <relativePath /> <!-- lookup parent from repository -->
11. </parent>
12. <groupId>infytel</groupId>
13. <artifactId>infytel_demo9</artifactId>
14. <version>0.0.1-SNAPSHOT</version>
15. <name>infytel_demo9</name>
16. <description>REST with Security</description>
17. <properties>
18. <java.version>1.8</java.version>
19. </properties>
20. <dependencies>
21. <dependency>
22. <groupId>org.springframework.boot</groupId>
23. <artifactId>spring-boot-starter-web</artifactId>
24. </dependency>
25. <!-- for security -->
26. <dependency>
27. <groupId>org.springframework.boot</groupId>
28. <artifactId>spring-boot-starter-security</artifactId>
29. </dependency>
30. <dependency>
31. <groupId>org.springframework.boot</groupId>
32. <artifactId>spring-boot-starter-test</artifactId>
33. <scope>test</scope>
34. </dependency>
35. </dependencies>
36. <build>
37. <plugins>
38. <plugin>
39. <groupId>org.springframework.boot</groupId>
40. <artifactId>spring-boot-maven-plugin</artifactId>
41. </plugin>
42. </plugins>
43. </build>
44. </project>
Step 12: Deploy the application on the server by executing the class containing the main method.
So, we have successfully created and deployed the REST endpoints. Now, let us see how can we test the
same using Postman client.
Output
Testing Web Service using Postman
Step 1: Launch Postman.
Step 2: Test this URL - http://localhost:8080/infytel-9/customers using HTTP GET. Without
authenticating, these tools cannot fetch the response. And, following will be the response
Set the credentials before submitting the request and get the customer details as the response.
Note: We can even test this URL - http://localhost:8080/infytel-9/customers using any standard web
browser. No customer details will be rendered and a pop up will appear that asks for the credentials.
Provide the username and password configured that are configured in our Security config class.