Skip to content

Instantly share code, notes, and snippets.

@arawn
Last active August 29, 2015 14:24
Show Gist options
  • Save arawn/57ff46f153898a565d61 to your computer and use it in GitHub Desktop.
Save arawn/57ff46f153898a565d61 to your computer and use it in GitHub Desktop.
Hands On Lab
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import demo.board.Board;
import demo.board.Post;
import demo.board.module.BoardService;
import demo.board.module.PostForm;
import demo.board.module.ResourceNotFoundException;
@RestController
@RequestMapping("/board")
public class BoardController {
private BoardService boardService;
private MessageSource messageSource;
@RequestMapping("/{boardname}/info")
public ResponseEntity<Board> boardInfo(@PathVariable String boardname) {
Board board = boardService.findBoard(boardname);
return ResponseEntity.ok(board);
}
@RequestMapping(value = "/{boardname}", method = { RequestMethod.GET, RequestMethod.HEAD })
public ResponseEntity<List<Post>> listPosts(@PathVariable String boardname) {
List<Post> posts = boardService.findPosts(boardname);
return ResponseEntity.ok(posts);
}
@RequestMapping(value = "/{boardname}", method = { RequestMethod.POST })
public ResponseEntity<Post> createPost(@PathVariable String boardname, @Valid PostForm postForm) {
Post post = boardService.writePost(boardname, postForm);
return ResponseEntity.ok(post);
}
@RequestMapping(value = "/{boardname}/{postId}", method = { RequestMethod.PUT })
public ResponseEntity<Post> updatePost(@PathVariable Long postId, PostForm postForm) {
Post post = boardService.editPost(postId, postForm);
return ResponseEntity.ok(post);
}
@RequestMapping(value = "/{boardname}/{postId}", method = { RequestMethod.DELETE })
public ResponseEntity<Post> deletePost(@PathVariable Long postId) {
Post post = boardService.erasePost(postId);
return ResponseEntity.ok(post);
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Map<String, Object>> handleResourceNotFoundException(ResourceNotFoundException e, Locale locale) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", new Date());
body.put("status", e.getStatus().value());
body.put("error", e.getError());
body.put("message", messageSource.getMessage(e.getCode(), e.getArgs(), "No message available", locale));
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body);
}
@ExceptionHandler(BindException.class)
public ResponseEntity<Map<String, Object>> handleBindException(BindException e, Locale locale) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", new Date());
body.put("status", HttpStatus.BAD_REQUEST.value());
body.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
body.put("message", messageSource.getMessage("error.BindException", null, "No message available", locale));
List<String> errors = new ArrayList<>();
e.getAllErrors().forEach(error -> {
errors.add(messageSource.getMessage(error, locale));
});
body.put("errors", errors);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body);
}
@Autowired
public void setBoardService(BoardService boardService) {
this.boardService = boardService;
}
@Autowired
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
}
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.MatcherAssertionErrors;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import demo.HandsonlabApplication;
import demo.board.Post;
import demo.board.module.BoardNotFoundException;
import demo.board.module.BoardService;
import demo.board.module.PostForm;
import demo.board.module.PostNotFoundException;
import demo.board.module.ResourceNotFoundException;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = HandsonlabApplication.class)
@WebIntegrationTest(randomPort = true)
public class BoardControllerTest {
private static final String PATH_BOARDINFO = "/board/{boardname}/info";
private static final String PATH_POST = "/board/{boardname}";
private static final String PATH_UPDATE_POST = "/board/{boardname}/{postId}";
private static final String TEST_BOARDNAME = "notice";
@Value("${local.server.port}")
private int serverPort;
@Autowired
private BoardService boardService;
private TestRestTemplate restTemplate;
@Before
public void setUp() {
this.restTemplate = new TestRestTemplate();
this.restTemplate.getInterceptors().add((request, body, execution) -> {
request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
return execution.execute(request, body);
});
}
@Test
public void 공지사항_게시판_정보_확인() {
UriComponentsBuilder uriBuilder = uriBuilderFromPath(PATH_BOARDINFO);
ResponseEntity<JsonNode> resposne = restTemplate.getForEntity(uriBuilder.buildAndExpand(TEST_BOARDNAME).toUri(), JsonNode.class);
MatcherAssertionErrors.assertThat(resposne.getStatusCode(), Matchers.is(HttpStatus.OK));
MatcherAssertionErrors.assertThat(resposne.getBody().path("name").asText(), Matchers.equalTo(TEST_BOARDNAME));
}
@Test
public void 게시판이_없으면_BoardNotFoundException_발생() {
UriComponentsBuilder uriBuilder = uriBuilderFromPath(PATH_BOARDINFO);
ResponseEntity<JsonNode> resposne = restTemplate.getForEntity(uriBuilder.buildAndExpand("error").toUri(), JsonNode.class);
MatcherAssertionErrors.assertThat(resposne.getStatusCode(), Matchers.equalTo(HttpStatus.NOT_FOUND));
assertNotFound(resposne.getBody(), new BoardNotFoundException(TEST_BOARDNAME));
}
@Test
public void 게시물_목록_요청() {
boardService.writePost(TEST_BOARDNAME, new PostForm("tester", "Hi~ 1", "No Content"));
boardService.writePost(TEST_BOARDNAME, new PostForm("tester", "Hi~ 2", "No Content"));
List<Post> posts = boardService.findPosts(TEST_BOARDNAME);
UriComponentsBuilder uriBuilder = uriBuilderFromPath(PATH_POST);
ResponseEntity<ArrayNode> resposne = restTemplate.getForEntity(uriBuilder.buildAndExpand(TEST_BOARDNAME).toUri(), ArrayNode.class);
MatcherAssertionErrors.assertThat(resposne.getStatusCode(), Matchers.is(HttpStatus.OK));
MatcherAssertionErrors.assertThat(resposne.getBody().size(), Matchers.is(posts.size()));
}
@Test
public void 게시물_쓰기_요청() {
URI uriCreatePost = uriBuilderFromPath(PATH_POST).buildAndExpand(TEST_BOARDNAME).toUri();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("author", "tester");
form.add("title", UUID.randomUUID().toString());
form.add("content", UUID.randomUUID().toString());
ResponseEntity<JsonNode> resposne = restTemplate.exchange(new RequestEntity<>(form, headers, HttpMethod.POST, uriCreatePost), JsonNode.class);
MatcherAssertionErrors.assertThat(resposne.getStatusCode(), Matchers.is(HttpStatus.OK));
assertPost(form, resposne.getBody());
}
@Test
public void 게시물_수정_요청() {
Post origin = boardService.writePost(TEST_BOARDNAME, new PostForm("tester", "Hi~", "No Content"));
URI uriUpdatePost = uriBuilderFromPath(PATH_UPDATE_POST).buildAndExpand(TEST_BOARDNAME, origin.getId()).toUri();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("author", "tester");
form.add("title", UUID.randomUUID().toString());
form.add("content", UUID.randomUUID().toString());
ResponseEntity<JsonNode> resposne = restTemplate.exchange(new RequestEntity<>(form, headers, HttpMethod.PUT, uriUpdatePost), JsonNode.class);
MatcherAssertionErrors.assertThat(resposne.getStatusCode(), Matchers.is(HttpStatus.OK));
assertPost(form, resposne.getBody());
}
private void assertPost(MultiValueMap<String, Object> form, JsonNode body) {
MatcherAssertionErrors.assertThat(body.path("author").asText(), Matchers.equalTo(form.get("author").get(0)));
MatcherAssertionErrors.assertThat(body.path("title").asText(), Matchers.equalTo(form.get("title").get(0)));
MatcherAssertionErrors.assertThat(body.path("content").asText(), Matchers.equalTo(form.get("content").get(0)));
}
@Test
public void 수정할_게시물이_없으면_PostNotFoundException_발생() {
URI uriUpdatePost = uriBuilderFromPath(PATH_UPDATE_POST).buildAndExpand(TEST_BOARDNAME, Integer.MAX_VALUE).toUri();
ResponseEntity<JsonNode> resposne = restTemplate.exchange(new RequestEntity<>(HttpMethod.PUT, uriUpdatePost), JsonNode.class);
MatcherAssertionErrors.assertThat(resposne.getStatusCode(), Matchers.equalTo(HttpStatus.NOT_FOUND));
assertNotFound(resposne.getBody(), new PostNotFoundException(Integer.MAX_VALUE));
}
private void assertNotFound(JsonNode body, ResourceNotFoundException resourceNotFoundException) {
MatcherAssertionErrors.assertThat(body.path("status").asInt(), Matchers.is(resourceNotFoundException.getStatus().value()));
MatcherAssertionErrors.assertThat(body.path("error").asText(), Matchers.equalTo(resourceNotFoundException.getError()));
MatcherAssertionErrors.assertThat(body.has("message"), Matchers.is(true));
}
private UriComponentsBuilder uriBuilderFromPath(String path) {
return UriComponentsBuilder.fromPath(path).scheme("http").host("localhost").port(serverPort);
}
}
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Service;
import demo.board.Board;
import demo.board.Post;
@Service
public class BoardService {
private Map<String, Board> boards = new LinkedHashMap<>();
private List<Post> posts = new ArrayList<Post>();
private AtomicLong postNumberGenerator = new AtomicLong(0);
@PostConstruct
public void init() {
this.boards.put("notice", new Board("notice"));
this.boards.put("random", new Board("random"));
this.posts.add(new Post(0, "tester", "no title,", "no content", boards.get("notice")));
}
public Board findBoard(String boardname) {
Board board = boards.get(boardname);
if(Objects.isNull(board)) {
throw new BoardNotFoundException(boardname);
}
return board;
}
public List<Post> findPosts(String boardname) {
Board board = findBoard(boardname);
return posts.stream()
.filter(post -> board.equals(post.getOwner()))
.collect(Collectors.toList());
}
public Post writePost(String boardname, String author, String title, String content) {
Board board = findBoard(boardname);
Post post = new Post(postNumberGenerator.incrementAndGet(), author, title, content, board);
posts.add(post);
return post;
}
public Post editPost(long postId, String author, String title, String content) {
return posts.stream()
.filter(post -> postId == post.getId())
.findAny()
.orElseThrow(() -> new PostNotFoundException(postId))
.update(author, title, content);
}
public Post erasePost(long postId) {
Post post = posts.stream()
.filter(_post -> postId == _post.getId())
.findAny()
.orElseThrow(() -> new PostNotFoundException(postId));
posts.remove(post);
return post;
}
}
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;
@RestController
public class ErrorHandler implements ErrorController {
private String errorPath;
private ErrorAttributes errorAttributes;
private MessageSource messageSource;
private MessageSourceAccessor messageSourceAccessor;
@RequestMapping(value = "${error.path:/error}")
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request, Locale locale) {
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
Map<String, Object> body = errorAttributes.getErrorAttributes(requestAttributes, false);
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
if(Objects.nonNull(body.get("status"))) {
try {
status = HttpStatus.valueOf((Integer) body.get("status"));
} catch(Exception ignore) { }
}
String message = messageSource.getMessage("error." + status, null, null, locale);
if(Objects.nonNull(message)) {
body.put("message", message);
}
/* Java 8
messageSourceAccessor.getMessage("error." + status, locale)
.ifPresent(message -> body.put("message", message));
*/
return ResponseEntity.status(status).body(body);
}
@Override
public String getErrorPath() {
return errorPath;
}
@Value("${error.path:/error}")
public void setErrorPath(String errorPath) {
this.errorPath = errorPath;
}
@Autowired
public void setErrorAttributes(ErrorAttributes errorAttributes) {
this.errorAttributes = errorAttributes;
}
@Autowired
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
this.messageSourceAccessor = new MessageSourceAccessor(messageSource);
}
}
error.405=요청 URL이 올바르지 않습니다.
error.ResourceNotFoundException=요청 자원을 찾을 수 없습니다.
error.BoardNotFoundException={0} 게시판을 찾을 수 없습니다.
error.PostNotFoundException={0}번 글을 찾을 수 없습니다.
error.BindException=전송 데이터가 올바르지 않습니다.
NotEmpty.postForm.author=작성자(author)는 필수 입력 값입니다.
NotEmpty.postForm.title=제목(title)은 필수 입력 값입니다.
NotEmpty.postForm.content=내용(content)은 필수 입력 값입니다.
import java.util.Date;
public class Post {
private Long id;
private String author;
private String title;
private String content;
private Date createdAt;
private Board owner;
public Post(long id, String author, String title, String content, Board owner) {
this.id = id;
this.author = author;
this.title = title;
this.content = content;
this.createdAt = new Date();
this.owner = owner;
}
public Long getId() {
return id;
}
public String getAuthor() {
return author;
}
public String getTitle() {
return title;
}
public String getContent() {
return content;
}
public Date getCreatedAt() {
return createdAt;
}
public Board getOwner() {
return owner;
}
public Post update(String author, String title, String content) {
this.author = author;
this.title = title;
this.content = content;
return this;
}
}
public class PostNotFoundException extends RuntimeException {
private final long postId;
public PostNotFoundException(long postId) {
this.postId = postId;
}
public Object[] getArgs() {
return new Object[]{ postId };
}
}
import org.springframework.http.HttpStatus;
public abstract class ResourceNotFoundException extends RuntimeException {
public HttpStatus getStatus() {
return HttpStatus.NOT_FOUND;
}
public String getError() {
return HttpStatus.NOT_FOUND.getReasonPhrase();
}
public String getCode() {
return "error.ResourceNotFoundException";
}
public Object[] getArgs() {
return new Object[0];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment