From f73893bbb90600c249bb9402fe190e90000ba926 Mon Sep 17 00:00:00 2001 From: Doha2012 Date: Mon, 12 Jun 2017 22:12:54 +0200 Subject: [PATCH] spring boot bootstrap (#2031) * minor logging fix * spring security sso * use basic auth * use form login * cleanup * cleanup * final cleanup * second client app for sso * spring boot bootstrap * add logic * cleanup * add simple controller * add thymeleaf and security * minor fix * minor fix * add more boot properties --- spring-boot-bootstrap/.gitignore | 24 +++ spring-boot-bootstrap/pom.xml | 75 +++++++++ .../main/java/org/baeldung/Application.java | 19 +++ .../org/baeldung/persistence/model/Book.java | 96 ++++++++++++ .../persistence/repo/BookRepository.java | 10 ++ .../java/org/baeldung/web/BookController.java | 74 +++++++++ .../baeldung/web/RestExceptionHandler.java | 32 ++++ .../org/baeldung/web/SimpleController.java | 19 +++ .../exception/BookIdMismatchException.java | 20 +++ .../web/exception/BookNotFoundException.java | 20 +++ .../src/main/resources/application.properties | 21 +++ .../src/main/resources/templates/error.html | 19 +++ .../src/main/resources/templates/home.html | 7 + .../src/test/java/org/baeldung/LiveTest.java | 142 ++++++++++++++++++ .../SpringBootBootstrapApplicationTests.java | 16 ++ 15 files changed, 594 insertions(+) create mode 100644 spring-boot-bootstrap/.gitignore create mode 100644 spring-boot-bootstrap/pom.xml create mode 100644 spring-boot-bootstrap/src/main/java/org/baeldung/Application.java create mode 100644 spring-boot-bootstrap/src/main/java/org/baeldung/persistence/model/Book.java create mode 100644 spring-boot-bootstrap/src/main/java/org/baeldung/persistence/repo/BookRepository.java create mode 100644 spring-boot-bootstrap/src/main/java/org/baeldung/web/BookController.java create mode 100644 spring-boot-bootstrap/src/main/java/org/baeldung/web/RestExceptionHandler.java create mode 100644 spring-boot-bootstrap/src/main/java/org/baeldung/web/SimpleController.java create mode 100644 spring-boot-bootstrap/src/main/java/org/baeldung/web/exception/BookIdMismatchException.java create mode 100644 spring-boot-bootstrap/src/main/java/org/baeldung/web/exception/BookNotFoundException.java create mode 100644 spring-boot-bootstrap/src/main/resources/application.properties create mode 100644 spring-boot-bootstrap/src/main/resources/templates/error.html create mode 100644 spring-boot-bootstrap/src/main/resources/templates/home.html create mode 100644 spring-boot-bootstrap/src/test/java/org/baeldung/LiveTest.java create mode 100644 spring-boot-bootstrap/src/test/java/org/baeldung/SpringBootBootstrapApplicationTests.java diff --git a/spring-boot-bootstrap/.gitignore b/spring-boot-bootstrap/.gitignore new file mode 100644 index 0000000000..2af7cefb0a --- /dev/null +++ b/spring-boot-bootstrap/.gitignore @@ -0,0 +1,24 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ \ No newline at end of file diff --git a/spring-boot-bootstrap/pom.xml b/spring-boot-bootstrap/pom.xml new file mode 100644 index 0000000000..697a139eb5 --- /dev/null +++ b/spring-boot-bootstrap/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + org.baeldung + spring-boot-bootstrap + 0.0.1-SNAPSHOT + jar + + spring-boot-bootstrap + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.5.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.h2database + h2 + + + org.springframework.boot + spring-boot-starter-security + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + io.rest-assured + rest-assured + 3.0.3 + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/spring-boot-bootstrap/src/main/java/org/baeldung/Application.java b/spring-boot-bootstrap/src/main/java/org/baeldung/Application.java new file mode 100644 index 0000000000..f7e7bb0347 --- /dev/null +++ b/spring-boot-bootstrap/src/main/java/org/baeldung/Application.java @@ -0,0 +1,19 @@ +package org.baeldung; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@SpringBootApplication +@ComponentScan("org.baeldung") +@EnableJpaRepositories("org.baeldung.persistence.repo") +@EntityScan("org.baeldung.persistence.model") +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/spring-boot-bootstrap/src/main/java/org/baeldung/persistence/model/Book.java b/spring-boot-bootstrap/src/main/java/org/baeldung/persistence/model/Book.java new file mode 100644 index 0000000000..02060e17c9 --- /dev/null +++ b/spring-boot-bootstrap/src/main/java/org/baeldung/persistence/model/Book.java @@ -0,0 +1,96 @@ +package org.baeldung.persistence.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Book { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + + @Column(nullable = false, unique = true) + private String title; + + @Column(nullable = false) + private String author; + + // + + public Book() { + super(); + } + + public Book(String title, String author) { + super(); + this.title = title; + this.author = author; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((author == null) ? 0 : author.hashCode()); + result = prime * result + (int) (id ^ (id >>> 32)); + result = prime * result + ((title == null) ? 0 : title.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Book other = (Book) obj; + if (author == null) { + if (other.author != null) + return false; + } else if (!author.equals(other.author)) + return false; + if (id != other.id) + return false; + if (title == null) { + if (other.title != null) + return false; + } else if (!title.equals(other.title)) + return false; + return true; + } + + @Override + public String toString() { + return "Book [id=" + id + ", title=" + title + ", author=" + author + "]"; + } + +} \ No newline at end of file diff --git a/spring-boot-bootstrap/src/main/java/org/baeldung/persistence/repo/BookRepository.java b/spring-boot-bootstrap/src/main/java/org/baeldung/persistence/repo/BookRepository.java new file mode 100644 index 0000000000..ec3a3e25bc --- /dev/null +++ b/spring-boot-bootstrap/src/main/java/org/baeldung/persistence/repo/BookRepository.java @@ -0,0 +1,10 @@ +package org.baeldung.persistence.repo; + +import java.util.List; + +import org.baeldung.persistence.model.Book; +import org.springframework.data.repository.CrudRepository; + +public interface BookRepository extends CrudRepository { + List findByTitle(String title); +} diff --git a/spring-boot-bootstrap/src/main/java/org/baeldung/web/BookController.java b/spring-boot-bootstrap/src/main/java/org/baeldung/web/BookController.java new file mode 100644 index 0000000000..544ac80217 --- /dev/null +++ b/spring-boot-bootstrap/src/main/java/org/baeldung/web/BookController.java @@ -0,0 +1,74 @@ +package org.baeldung.web; + +import java.util.List; + +import org.baeldung.persistence.model.Book; +import org.baeldung.persistence.repo.BookRepository; +import org.baeldung.web.exception.BookIdMismatchException; +import org.baeldung.web.exception.BookNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/books") +public class BookController { + + @Autowired + private BookRepository bookRepository; + + @GetMapping + public Iterable findAll() { + return bookRepository.findAll(); + } + + @GetMapping("/title/{bookTitle}") + public List findByTitle(@PathVariable String bookTitle) { + return bookRepository.findByTitle(bookTitle); + } + + @GetMapping("/{id}") + public Book findOne(@PathVariable Long id) { + final Book book = bookRepository.findOne(id); + if (book == null) { + throw new BookNotFoundException(); + } + return book; + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Book create(@RequestBody Book book) { + return bookRepository.save(book); + } + + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + final Book book = bookRepository.findOne(id); + if (book == null) { + throw new BookNotFoundException(); + } + bookRepository.delete(id); + } + + @PutMapping("/{id}") + public Book updateBook(@RequestBody Book book, @PathVariable Long id) { + if (book.getId() != id) { + throw new BookIdMismatchException(); + } + final Book old = bookRepository.findOne(id); + if (old == null) { + throw new BookNotFoundException(); + } + return bookRepository.save(book); + } + +} diff --git a/spring-boot-bootstrap/src/main/java/org/baeldung/web/RestExceptionHandler.java b/spring-boot-bootstrap/src/main/java/org/baeldung/web/RestExceptionHandler.java new file mode 100644 index 0000000000..60153fc92f --- /dev/null +++ b/spring-boot-bootstrap/src/main/java/org/baeldung/web/RestExceptionHandler.java @@ -0,0 +1,32 @@ +package org.baeldung.web; + +import org.baeldung.web.exception.BookIdMismatchException; +import org.baeldung.web.exception.BookNotFoundException; +import org.hibernate.exception.ConstraintViolationException; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@ControllerAdvice +public class RestExceptionHandler extends ResponseEntityExceptionHandler { + + public RestExceptionHandler() { + super(); + } + + @ExceptionHandler({ BookNotFoundException.class }) + protected ResponseEntity handleNotFound(Exception ex, WebRequest request) { + return handleExceptionInternal(ex, "Book not found", new HttpHeaders(), HttpStatus.NOT_FOUND, request); + } + + @ExceptionHandler({ BookIdMismatchException.class, ConstraintViolationException.class, DataIntegrityViolationException.class }) + public ResponseEntity handleBadRequest(Exception ex, WebRequest request) { + return handleExceptionInternal(ex, ex.getLocalizedMessage(), new HttpHeaders(), HttpStatus.BAD_REQUEST, request); + } + +} \ No newline at end of file diff --git a/spring-boot-bootstrap/src/main/java/org/baeldung/web/SimpleController.java b/spring-boot-bootstrap/src/main/java/org/baeldung/web/SimpleController.java new file mode 100644 index 0000000000..0bec973a75 --- /dev/null +++ b/spring-boot-bootstrap/src/main/java/org/baeldung/web/SimpleController.java @@ -0,0 +1,19 @@ +package org.baeldung.web; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class SimpleController { + @Value("${spring.application.name}") + String appName; + + @RequestMapping("/") + public String homePage(Model model) { + model.addAttribute("appName", appName); + return "home"; + } + +} diff --git a/spring-boot-bootstrap/src/main/java/org/baeldung/web/exception/BookIdMismatchException.java b/spring-boot-bootstrap/src/main/java/org/baeldung/web/exception/BookIdMismatchException.java new file mode 100644 index 0000000000..23c55e2d38 --- /dev/null +++ b/spring-boot-bootstrap/src/main/java/org/baeldung/web/exception/BookIdMismatchException.java @@ -0,0 +1,20 @@ +package org.baeldung.web.exception; + +public class BookIdMismatchException extends RuntimeException { + + public BookIdMismatchException() { + super(); + } + + public BookIdMismatchException(final String message, final Throwable cause) { + super(message, cause); + } + + public BookIdMismatchException(final String message) { + super(message); + } + + public BookIdMismatchException(final Throwable cause) { + super(cause); + } +} diff --git a/spring-boot-bootstrap/src/main/java/org/baeldung/web/exception/BookNotFoundException.java b/spring-boot-bootstrap/src/main/java/org/baeldung/web/exception/BookNotFoundException.java new file mode 100644 index 0000000000..3ff416b3f3 --- /dev/null +++ b/spring-boot-bootstrap/src/main/java/org/baeldung/web/exception/BookNotFoundException.java @@ -0,0 +1,20 @@ +package org.baeldung.web.exception; + +public class BookNotFoundException extends RuntimeException { + + public BookNotFoundException() { + super(); + } + + public BookNotFoundException(final String message, final Throwable cause) { + super(message, cause); + } + + public BookNotFoundException(final String message) { + super(message); + } + + public BookNotFoundException(final Throwable cause) { + super(cause); + } +} \ No newline at end of file diff --git a/spring-boot-bootstrap/src/main/resources/application.properties b/spring-boot-bootstrap/src/main/resources/application.properties new file mode 100644 index 0000000000..b7e6b1e80e --- /dev/null +++ b/spring-boot-bootstrap/src/main/resources/application.properties @@ -0,0 +1,21 @@ +server.port = 8081 + +spring.application.name = Bootstrap Spring Boot + +spring.thymeleaf.cache = false +spring.thymeleaf.enabled=true +spring.thymeleaf.prefix=classpath:/templates/ +spring.thymeleaf.suffix=.html + +security.basic.enabled=true +security.user.name=john +security.user.password=123 + + +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:bootapp;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.username=sa +spring.datasource.password= + +server.error.path=/error +server.error.whitelabel.enabled=false \ No newline at end of file diff --git a/spring-boot-bootstrap/src/main/resources/templates/error.html b/spring-boot-bootstrap/src/main/resources/templates/error.html new file mode 100644 index 0000000000..f2b51a1938 --- /dev/null +++ b/spring-boot-bootstrap/src/main/resources/templates/error.html @@ -0,0 +1,19 @@ + + +Error Occurred + + + +
+

Error Occurred!

+ +
+ + + \ No newline at end of file diff --git a/spring-boot-bootstrap/src/main/resources/templates/home.html b/spring-boot-bootstrap/src/main/resources/templates/home.html new file mode 100644 index 0000000000..5707cfb82a --- /dev/null +++ b/spring-boot-bootstrap/src/main/resources/templates/home.html @@ -0,0 +1,7 @@ + +Home Page + +

Hello !

+

Welcome to Our App

+ + \ No newline at end of file diff --git a/spring-boot-bootstrap/src/test/java/org/baeldung/LiveTest.java b/spring-boot-bootstrap/src/test/java/org/baeldung/LiveTest.java new file mode 100644 index 0000000000..5ecd4023bd --- /dev/null +++ b/spring-boot-bootstrap/src/test/java/org/baeldung/LiveTest.java @@ -0,0 +1,142 @@ +package org.baeldung; + +import static io.restassured.RestAssured.preemptive; +import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; +import static org.apache.commons.lang3.RandomStringUtils.randomNumeric; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import io.restassured.RestAssured; +import io.restassured.response.Response; + +import java.util.List; + +import org.baeldung.persistence.model.Book; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { Application.class }, webEnvironment = WebEnvironment.DEFINED_PORT) +public class LiveTest { + + @Before + public void setUp() { + RestAssured.authentication = preemptive().basic("john", "123"); + } + + private static final String API_ROOT = "http://localhost:8081/api/books"; + + @Test + public void whenGetAllBooks_thenOK() { + final Response response = RestAssured.get(API_ROOT); + assertEquals(HttpStatus.OK.value(), response.getStatusCode()); + } + + @Test + public void whenGetBooksByTitle_thenOK() { + final Book book = createRandomBook(); + createBookAsUri(book); + + final Response response = RestAssured.get(API_ROOT + "/title/" + book.getTitle()); + assertEquals(HttpStatus.OK.value(), response.getStatusCode()); + assertTrue(response.as(List.class) + .size() > 0); + } + + @Test + public void whenGetCreatedBookById_thenOK() { + final Book book = createRandomBook(); + final String location = createBookAsUri(book); + + final Response response = RestAssured.get(location); + assertEquals(HttpStatus.OK.value(), response.getStatusCode()); + assertEquals(book.getTitle(), response.jsonPath() + .get("title")); + } + + @Test + public void whenGetNotExistBookById_thenNotFound() { + final Response response = RestAssured.get(API_ROOT + "/" + randomNumeric(4)); + assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode()); + } + + // POST + @Test + public void whenCreateNewBook_thenCreated() { + final Book book = createRandomBook(); + + final Response response = RestAssured.given() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(book) + .post(API_ROOT); + assertEquals(HttpStatus.CREATED.value(), response.getStatusCode()); + } + + @Test + public void whenInvalidBook_thenError() { + final Book book = createRandomBook(); + book.setAuthor(null); + + final Response response = RestAssured.given() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(book) + .post(API_ROOT); + assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusCode()); + } + + @Test + public void whenUpdateCreatedBook_thenUpdated() { + final Book book = createRandomBook(); + final String location = createBookAsUri(book); + + book.setId(Long.parseLong(location.split("api/books/")[1])); + book.setAuthor("newAuthor"); + Response response = RestAssured.given() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(book) + .put(location); + assertEquals(HttpStatus.OK.value(), response.getStatusCode()); + + response = RestAssured.get(location); + assertEquals(HttpStatus.OK.value(), response.getStatusCode()); + assertEquals("newAuthor", response.jsonPath() + .get("author")); + + } + + @Test + public void whenDeleteCreatedBook_thenOk() { + final Book book = createRandomBook(); + final String location = createBookAsUri(book); + + Response response = RestAssured.delete(location); + assertEquals(HttpStatus.OK.value(), response.getStatusCode()); + + response = RestAssured.get(location); + assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode()); + } + + // =============================== + + private Book createRandomBook() { + final Book book = new Book(); + book.setTitle(randomAlphabetic(10)); + book.setAuthor(randomAlphabetic(15)); + return book; + } + + private String createBookAsUri(Book book) { + final Response response = RestAssured.given() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(book) + .post(API_ROOT); + return API_ROOT + "/" + response.jsonPath() + .get("id"); + } + +} \ No newline at end of file diff --git a/spring-boot-bootstrap/src/test/java/org/baeldung/SpringBootBootstrapApplicationTests.java b/spring-boot-bootstrap/src/test/java/org/baeldung/SpringBootBootstrapApplicationTests.java new file mode 100644 index 0000000000..f32315ef03 --- /dev/null +++ b/spring-boot-bootstrap/src/test/java/org/baeldung/SpringBootBootstrapApplicationTests.java @@ -0,0 +1,16 @@ +package org.baeldung; + +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 SpringBootBootstrapApplicationTests { + + @Test + public void contextLoads() { + } + +}