- Step 01 - Initializing a RESTful Services Project with Spring Boot
- Step 02 - Understanding the RESTful Services we would create in this course
- Step 03 - Creating a Hello World Service
- Step 04 - Enhancing the Hello World Service to return a Bean
- Step 05 - Quick Review of Spring Boot Auto Configuration and Dispatcher Servlet - What's happening in the background?
- Step 06 - Enhancing the Hello World Service with a Path Variable
- Step 07 - Creating User Bean and User Service
- Step 08 - Implementing GET Methods for User Resource
- Step 09 - Implementing POST Method to create User Resource
- Step 10 - Enhancing POST Method to return correct HTTP Status Code and Location URI
- Step 11 - Implementing Exception Handling - 404 Resource Not Found
- Step 12 - Implementing Generic Exception Handling for all Resources
- Step 13 - Exercise : User Post Resource and Exception Handling
- Step 14 - Implementing DELETE Method to delete a User Resource
- Step 15 - Implementing Validations for RESTful Services
- Step 16 - Implementing HATEOAS for RESTful Services
- Step 17 - Overview of Advanced RESTful Service Features
- Step 18 - Internationalization for RESTful Services
- Step 19 - Content Negotiation - Implementing Support for XML
- Step 20 - Configuring Auto Generation of Swagger Documentation
- Step 21 - Introduction to Swagger Documentation Format
- Step 22 - Enhancing Swagger Documentation with Custom Annotations
- Step 23 - Monitoring APIs with Spring Boot Actuator
- Step 24 - Implementing Static Filtering for RESTful Service
- Step 25 - Implementing Dynamic Filtering for RESTful Service
- Step 26 - Versioning RESTful Services - Basic Approach with URIs
- Step 27 - Versioning RESTful Services - Header and Content Negotiation Approaches
- Step 28 - Implementing Basic Authentication with Spring Security
- Step 29 - Overview of Connecting RESTful Service to JPA
- Step 30 - Creating User Entity and some test data
- Step 31 - Updating GET methods on User Resource to use JPA
- Step 32 - Updating POST and DELETE methods on User Resource to use JPA
- Step 33 - Creating Post Entity and Many to One Relationship with User Entity
- Step 34 - Implementing a GET service to retrieve all Posts of a User
- Step 35 - Implementing a POST service to create a Post for a User
We will help you install
- Java 8
- Eclipse
- Maven
- Embedded Tomcat
- Postman REST Services Client
- POSTMAN - http://www.getpostman.com
- Basic Resources
- JPA Resources
- Filtering
- Actuator
- Versioning
- http://localhost:8080/v1/person
- http://localhost:8080/v2/person
- http://localhost:8080/person/param
- params=[version=1]
- http://localhost:8080/person/param
- params=[version=2]
- http://localhost:8080/person/header
- headers=[X-API-VERSION=1]
- http://localhost:8080/person/header
- headers=[X-API-VERSION=2]
- http://localhost:8080/person/produces
- produces=[application/vnd.company.app-v1+json]
- http://localhost:8080/person/produces
- produces=[application/vnd.company.app-v2+json]
- Swagger
- H2-Console
create table user (
id integer not null,
birth_date timestamp,
name varchar(255),
primary key (id)
);
create table post (
id integer not null,
description varchar(255),
user_id integer,
primary key (id)
);
alter table post
add constraint post_to_user_foreign_key
foreign key (user_id) references user;
[
{
"id": 1,
"name": "Adam",
"birthDate": "2017-07-19T04:40:20.796+0000"
},
{
"id": 2,
"name": "Eve",
"birthDate": "2017-07-19T04:40:20.796+0000"
},
{
"id": 3,
"name": "Jack",
"birthDate": "2017-07-19T04:40:20.796+0000"
}
]
{
"id": 1,
"name": "Adam",
"birthDate": "2017-07-19T04:40:20.796+0000"
}
{
"name": "Ranga",
"birthDate": "2000-07-19T04:29:24.054+0000"
}
- Get request to a non existing resource.
- The response shows default error message structure auto configured by Spring Boot.
{
"timestamp": "2017-07-19T05:28:37.534+0000",
"status": 404,
"error": "Not Found",
"message": "id-500",
"path": "/users/500"
}
- Get request to a non existing resource.
- The response shows a Customized Message Structure
{
"timestamp": "2017-07-19T05:31:01.961+0000",
"message": "id-500",
"details": "Any details you would want to add"
}
POST http://localhost:8080/users with Validation Errors
{
"name": "R",
"birthDate": "2000-07-19T04:29:24.054+0000"
}
{
"timestamp": "2017-07-19T09:00:27.912+0000",
"message": "Validation Failed",
"details": "org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object 'user' on field 'name': rejected value [R]; codes [Size.user.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name],2147483647,2]; default message [Name should have atleast 2 characters]"
}
GET http://localhost:8080/users/1 with HATEOAS
{
"id": 1,
"name": "Adam",
"birthDate": "2017-07-19T09:26:18.337+0000",
"_links": {
"all-users": {
"href": "http://localhost:8080/users"
}
}
}
- Accept application/xml
<List>
<item>
<id>2</id>
<name>Eve</name>
<birthDate>2017-07-19T10:25:20.450+0000</birthDate>
</item>
<item>
<id>3</id>
<name>Jack</name>
<birthDate>2017-07-19T10:25:20.450+0000</birthDate>
</item>
<item>
<id>4</id>
<name>Ranga</name>
<birthDate>2017-07-19T10:25:20.450+0000</birthDate>
</item>
</List>
- Accept : application/xml
- Content-Type : application/xml
<item>
<name>Ranga</name>
<birthDate>2017-07-19T10:25:20.450+0000</birthDate>
</item>
- Status - 201 Created
- Media type versioning (a.k.a “content negotiation” or “accept header”)
- GitHub
- (Custom) headers versioning
- Microsoft
- URI Versioning
- Request Parameter versioning
- Amazon
- Factors
- URI Pollution
- Misuse of HTTP Headers
- Caching
- Can we execute the request on the browser?
- API Documentation
- No Perfect Solution
- https://www.mnot.net/blog/2011/10/25/web_api_versioning_smackdown
- http://urthen.github.io/2013/05/09/ways-to-version-your-api/
- http://stackoverflow.com/questions/389169/best-practices-for-api-versioning
- http://www.lexicalscope.com/blog/2012/03/12/how-are-rest-apis-versioned/
- https://www.3scale.net/2016/06/api-versioning-methods-a-brief-reference/
-
Retrieve all Users - GET /users
-
Create a User - POST /users
-
Retrieve one User - GET /users/{id} -> /users/1
-
Delete a User - DELETE /users/{id} -> /users/1
-
Retrieve all posts for a User - GET /users/{id}/posts
-
Create a posts for a User - POST /users/{id}/posts
-
Retrieve details of a post - GET /users/{id}/posts/{post_id}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.in28minutes.rest.webservices</groupId>
<artifactId>restful-web-services</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>restful-web-services</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
/src/main/java/com/in28minutes/rest/webservices/restfulwebservices/exception/CustomizedResponseEntityExceptionHandler.java
package com.in28minutes.rest.webservices.restfulwebservices.exception;
import java.util.Date;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.in28minutes.rest.webservices.restfulwebservices.user.UserNotFoundException;
@ControllerAdvice
@RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(UserNotFoundException.class)
public final ResponseEntity<Object> handleUserNotFoundException(UserNotFoundException ex, WebRequest request) {
ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), "Validation Failed",
ex.getBindingResult().toString());
return new ResponseEntity(exceptionResponse, HttpStatus.BAD_REQUEST);
}
}
package com.in28minutes.rest.webservices.restfulwebservices.exception;
import java.util.Date;
public class ExceptionResponse {
private Date timestamp;
private String message;
private String details;
public ExceptionResponse(Date timestamp, String message, String details) {
super();
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
public Date getTimestamp() {
return timestamp;
}
public String getMessage() {
return message;
}
public String getDetails() {
return details;
}
}
/src/main/java/com/in28minutes/rest/webservices/restfulwebservices/filtering/FilteringController.java
package com.in28minutes.rest.webservices.restfulwebservices.filtering;
import java.util.Arrays;
import java.util.List;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
@RestController
public class FilteringController {
// field1,field2
@GetMapping("/filtering")
public MappingJacksonValue retrieveSomeBean() {
SomeBean someBean = new SomeBean("value1", "value2", "value3");
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field2");
FilterProvider filters = new SimpleFilterProvider().addFilter("SomeBeanFilter", filter);
MappingJacksonValue mapping = new MappingJacksonValue(someBean);
mapping.setFilters(filters);
return mapping;
}
// field2, field3
@GetMapping("/filtering-list")
public MappingJacksonValue retrieveListOfSomeBeans() {
List<SomeBean> list = Arrays.asList(new SomeBean("value1", "value2", "value3"),
new SomeBean("value12", "value22", "value32"));
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("field2", "field3");
FilterProvider filters = new SimpleFilterProvider().addFilter("SomeBeanFilter", filter);
MappingJacksonValue mapping = new MappingJacksonValue(list);
mapping.setFilters(filters);
return mapping;
}
}
package com.in28minutes.rest.webservices.restfulwebservices.filtering;
import com.fasterxml.jackson.annotation.JsonFilter;
@JsonFilter("SomeBeanFilter")
public class SomeBean {
private String field1;
private String field2;
private String field3;
public SomeBean(String field1, String field2, String field3) {
super();
this.field1 = field1;
this.field2 = field2;
this.field3 = field3;
}
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}
public String getField3() {
return field3;
}
public void setField3(String field3) {
this.field3 = field3;
}
}
package com.in28minutes.rest.webservices.restfulwebservices.helloworld;
public class HelloWorldBean {
private String message;
public HelloWorldBean(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return String.format("HelloWorldBean [message=%s]", message);
}
}
/src/main/java/com/in28minutes/rest/webservices/restfulwebservices/helloworld/HelloWorldController.java
package com.in28minutes.rest.webservices.restfulwebservices.helloworld;
import java.util.Locale;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
//Controller
@RestController
public class HelloWorldController {
@Autowired
private MessageSource messageSource;
@GetMapping(path = "/hello-world")
public String helloWorld() {
return "Hello World";
}
@GetMapping(path = "/hello-world-bean")
public HelloWorldBean helloWorldBean() {
return new HelloWorldBean("Hello World");
}
///hello-world/path-variable/in28minutes
@GetMapping(path = "/hello-world/path-variable/{name}")
public HelloWorldBean helloWorldPathVariable(@PathVariable String name) {
return new HelloWorldBean(String.format("Hello World, %s", name));
}
@GetMapping(path = "/hello-world-internationalized")
public String helloWorldInternationalized() {
return messageSource.getMessage("good.morning.message", null,
LocaleContextHolder.getLocale());
}
}
/src/main/java/com/in28minutes/rest/webservices/restfulwebservices/RestfulWebServicesApplication.java
package com.in28minutes.rest.webservices.restfulwebservices;
import java.util.Locale;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
@SpringBootApplication
public class RestfulWebServicesApplication {
public static void main(String[] args) {
SpringApplication.run(RestfulWebServicesApplication.class, args);
}
@Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(Locale.US);
return localeResolver;
}
}
package com.in28minutes.rest.webservices.restfulwebservices;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
public static final Contact DEFAULT_CONTACT = new Contact(
"Ranga Karanam", "http://www.in28minutes.com", "[email protected]");
public static final ApiInfo DEFAULT_API_INFO = new ApiInfo(
"Awesome API Title", "Awesome API Description", "1.0",
"urn:tos", DEFAULT_CONTACT,
"Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0");
private static final Set<String> DEFAULT_PRODUCES_AND_CONSUMES =
new HashSet<String>(Arrays.asList("application/json",
"application/xml"));
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(DEFAULT_API_INFO)
.produces(DEFAULT_PRODUCES_AND_CONSUMES)
.consumes(DEFAULT_PRODUCES_AND_CONSUMES);
}
}
package com.in28minutes.rest.webservices.restfulwebservices.user;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
public class Post {
@Id
@GeneratedValue
private Integer id;
private String description;
@ManyToOne(fetch=FetchType.LAZY)
@JsonIgnore
private User user;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return String.format("Post [id=%s, description=%s]", id, description);
}
}
package com.in28minutes.rest.webservices.restfulwebservices.user;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PostRepository extends JpaRepository<Post, Integer>{
}
package com.in28minutes.rest.webservices.restfulwebservices.user;
import java.util.Date;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel(description="All details about the user.")
@Entity
public class User {
@Id
@GeneratedValue
private Integer id;
@Size(min=2, message="Name should have atleast 2 characters")
@ApiModelProperty(notes="Name should have atleast 2 characters")
private String name;
@Past
@ApiModelProperty(notes="Birth date should be in the past")
private Date birthDate;
@OneToMany(mappedBy="user")
private List<Post> posts;
protected User() {
}
public User(Integer id, String name, Date birthDate) {
super();
this.id = id;
this.name = name;
this.birthDate = birthDate;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
public List<Post> getPosts() {
return posts;
}
public void setPosts(List<Post> posts) {
this.posts = posts;
}
@Override
public String toString() {
return String.format("User [id=%s, name=%s, birthDate=%s]", id, name, birthDate);
}
}
package com.in28minutes.rest.webservices.restfulwebservices.user;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.springframework.stereotype.Component;
@Component
public class UserDaoService {
private static List<User> users = new ArrayList<>();
private static int usersCount = 3;
static {
users.add(new User(1, "Adam", new Date()));
users.add(new User(2, "Eve", new Date()));
users.add(new User(3, "Jack", new Date()));
}
public List<User> findAll() {
return users;
}
public User save(User user) {
if (user.getId() == null) {
user.setId(++usersCount);
}
users.add(user);
return user;
}
public User findOne(int id) {
for (User user : users) {
if (user.getId() == id) {
return user;
}
}
return null;
}
public User deleteById(int id) {
Iterator<User> iterator = users.iterator();
while (iterator.hasNext()) {
User user = iterator.next();
if (user.getId() == id) {
iterator.remove();
return user;
}
}
return null;
}
}
package com.in28minutes.rest.webservices.restfulwebservices.user;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.net.URI;
import java.util.List;
import java.util.Optional;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
@RestController
public class UserJPAResource {
@Autowired
private UserRepository userRepository;
@Autowired
private PostRepository postRepository;
@GetMapping("/jpa/users")
public List<User> retrieveAllUsers() {
return userRepository.findAll();
}
@GetMapping("/jpa/users/{id}")
public Resource<User> retrieveUser(@PathVariable int id) {
Optional<User> user = userRepository.findById(id);
if (!user.isPresent())
throw new UserNotFoundException("id-" + id);
// "all-users", SERVER_PATH + "/users"
// retrieveAllUsers
Resource<User> resource = new Resource<User>(user.get());
ControllerLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
resource.add(linkTo.withRel("all-users"));
// HATEOAS
return resource;
}
@DeleteMapping("/jpa/users/{id}")
public void deleteUser(@PathVariable int id) {
userRepository.deleteById(id);
}
//
// input - details of user
// output - CREATED & Return the created URI
// HATEOAS
@PostMapping("/jpa/users")
public ResponseEntity<Object> createUser(@Valid @RequestBody User user) {
User savedUser = userRepository.save(user);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(savedUser.getId())
.toUri();
return ResponseEntity.created(location).build();
}
@GetMapping("/jpa/users/{id}/posts")
public List<Post> retrieveAllUsers(@PathVariable int id) {
Optional<User> userOptional = userRepository.findById(id);
if(!userOptional.isPresent()) {
throw new UserNotFoundException("id-" + id);
}
return userOptional.get().getPosts();
}
@PostMapping("/jpa/users/{id}/posts")
public ResponseEntity<Object> createPost(@PathVariable int id, @RequestBody Post post) {
Optional<User> userOptional = userRepository.findById(id);
if(!userOptional.isPresent()) {
throw new UserNotFoundException("id-" + id);
}
User user = userOptional.get();
post.setUser(user);
postRepository.save(post);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(post.getId())
.toUri();
return ResponseEntity.created(location).build();
}
}
package com.in28minutes.rest.webservices.restfulwebservices.user;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
package com.in28minutes.rest.webservices.restfulwebservices.user;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Integer>{
}
package com.in28minutes.rest.webservices.restfulwebservices.user;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.net.URI;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
@RestController
public class UserResource {
@Autowired
private UserDaoService service;
@GetMapping("/users")
public List<User> retrieveAllUsers() {
return service.findAll();
}
@GetMapping("/users/{id}")
public Resource<User> retrieveUser(@PathVariable int id) {
User user = service.findOne(id);
if(user==null)
throw new UserNotFoundException("id-"+ id);
//"all-users", SERVER_PATH + "/users"
//retrieveAllUsers
Resource<User> resource = new Resource<User>(user);
ControllerLinkBuilder linkTo =
linkTo(methodOn(this.getClass()).retrieveAllUsers());
resource.add(linkTo.withRel("all-users"));
//HATEOAS
return resource;
}
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable int id) {
User user = service.deleteById(id);
if(user==null)
throw new UserNotFoundException("id-"+ id);
}
//
// input - details of user
// output - CREATED & Return the created URI
//HATEOAS
@PostMapping("/users")
public ResponseEntity<Object> createUser(@Valid @RequestBody User user) {
User savedUser = service.save(user);
// CREATED
// /user/{id} savedUser.getId()
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(savedUser.getId()).toUri();
return ResponseEntity.created(location).build();
}
}
package com.in28minutes.rest.webservices.restfulwebservices.versioning;
public class Name {
private String firstName;
private String lastName;
public Name() {
}
public Name(String firstName, String lastName) {
super();
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
package com.in28minutes.rest.webservices.restfulwebservices.versioning;
public class PersonV1 {
private String name;
public PersonV1() {
super();
}
public PersonV1(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.in28minutes.rest.webservices.restfulwebservices.versioning;
public class PersonV2 {
private Name name;
public PersonV2() {
super();
}
public PersonV2(Name name) {
super();
this.name = name;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
}
/src/main/java/com/in28minutes/rest/webservices/restfulwebservices/versioning/PersonVersioningController.java
package com.in28minutes.rest.webservices.restfulwebservices.versioning;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PersonVersioningController {
@GetMapping("v1/person")
public PersonV1 personV1() {
return new PersonV1("Bob Charlie");
}
@GetMapping("v2/person")
public PersonV2 personV2() {
return new PersonV2(new Name("Bob", "Charlie"));
}
@GetMapping(value = "/person/param", params = "version=1")
public PersonV1 paramV1() {
return new PersonV1("Bob Charlie");
}
@GetMapping(value = "/person/param", params = "version=2")
public PersonV2 paramV2() {
return new PersonV2(new Name("Bob", "Charlie"));
}
@GetMapping(value = "/person/header", headers = "X-API-VERSION=1")
public PersonV1 headerV1() {
return new PersonV1("Bob Charlie");
}
@GetMapping(value = "/person/header", headers = "X-API-VERSION=2")
public PersonV2 headerV2() {
return new PersonV2(new Name("Bob", "Charlie"));
}
@GetMapping(value = "/person/produces", produces = "application/vnd.company.app-v1+json")
public PersonV1 producesV1() {
return new PersonV1("Bob Charlie");
}
@GetMapping(value = "/person/produces", produces = "application/vnd.company.app-v2+json")
public PersonV2 producesV2() {
return new PersonV2(new Name("Bob", "Charlie"));
}
}
logging.level.org.springframework = info
#This is not really needed as this is the default after 2.0.0.RELEASE
spring.jackson.serialization.write-dates-as-timestamps=false
spring.messages.basename=messages
management.endpoints.web.exposure.include=*
spring.security.user.name=username
spring.security.user.password=password
spring.jpa.show-sql=true
spring.h2.console.enabled=true
insert into user values(10001, sysdate(), 'AB');
insert into user values(10002, sysdate(), 'Jill');
insert into user values(10003, sysdate(), 'Jam');
insert into post values(11001, 'My First Post', 10001);
insert into post values(11002, 'My Second Post', 10001);
good.morning.message=Good Morning
good.morning.message=Bonjour
good.morning.message=Goede Morgen
/src/test/java/com/in28minutes/rest/webservices/restfulwebservices/RestfulWebServicesApplicationTests.java
package com.in28minutes.rest.webservices.restfulwebservices;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RestfulWebServicesApplicationTests {
@Test
public void contextLoads() {
}
}