Use HAL in the Spring HATEOAS-based sample
This commit is contained in:
@@ -31,6 +31,8 @@ dependencies {
|
||||
compile 'org.springframework.hateoas:spring-hateoas'
|
||||
|
||||
runtime 'com.h2database:h2'
|
||||
runtime 'org.springframework.plugin:spring-plugin-core:1.1.0.RELEASE'
|
||||
runtime 'org.atteo:evo-inflector:1.2'
|
||||
|
||||
documentationCompile 'com.jayway.jsonpath:json-path'
|
||||
documentationCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||
|
||||
@@ -16,16 +16,16 @@
|
||||
|
||||
package com.example.notes;
|
||||
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.springframework.restdocs.core.RestDocumentation.document;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.restdocs.core.RestDocumentation.document;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Arrays;
|
||||
@@ -38,7 +38,6 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.restdocs.core.RestDocumentationConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
@@ -74,11 +73,10 @@ public class GettingStartedDocumentation {
|
||||
public void index() throws Exception {
|
||||
document(
|
||||
"index",
|
||||
this.mockMvc
|
||||
.perform(get("/").accept(MediaTypes.HAL_JSON))
|
||||
this.mockMvc.perform(get("/").accept(MediaTypes.HAL_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("links[?(@.rel==notes)]", is(notNullValue())))
|
||||
.andExpect(jsonPath("links[?(@.rel==tags)]", is(notNullValue()))));
|
||||
.andExpect(jsonPath("_links.notes", is(notNullValue())))
|
||||
.andExpect(jsonPath("_links.tags", is(notNullValue()))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -121,7 +119,7 @@ public class GettingStartedDocumentation {
|
||||
this.mockMvc.perform(get(noteLocation)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("title", is(notNullValue())))
|
||||
.andExpect(jsonPath("body", is(notNullValue())))
|
||||
.andExpect(jsonPath("links[?(@.rel==tags)]", is(notNullValue()))));
|
||||
.andExpect(jsonPath("_links.tags", is(notNullValue()))));
|
||||
}
|
||||
|
||||
String createTag() throws Exception, JsonProcessingException {
|
||||
@@ -143,11 +141,9 @@ public class GettingStartedDocumentation {
|
||||
void getTag(String tagLocation) throws Exception {
|
||||
document(
|
||||
"get-tag",
|
||||
this.mockMvc
|
||||
.perform(get(tagLocation))
|
||||
.andExpect(status().isOk())
|
||||
this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("name", is(notNullValue())))
|
||||
.andExpect(jsonPath("links[?(@.rel==notes)]", is(notNullValue()))));
|
||||
.andExpect(jsonPath("_links.notes", is(notNullValue()))));
|
||||
}
|
||||
|
||||
String createTaggedNote(String tag) throws Exception {
|
||||
@@ -160,8 +156,8 @@ public class GettingStartedDocumentation {
|
||||
"create-tagged-note",
|
||||
this.mockMvc
|
||||
.perform(
|
||||
post("/notes").contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(note)))
|
||||
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
||||
objectMapper.writeValueAsString(note)))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(header().string("Location", notNullValue())))
|
||||
.andReturn().getResponse().getHeader("Location");
|
||||
@@ -174,7 +170,7 @@ public class GettingStartedDocumentation {
|
||||
this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("title", is(notNullValue())))
|
||||
.andExpect(jsonPath("body", is(notNullValue())))
|
||||
.andExpect(jsonPath("links[?(@.rel==tags)]", is(notNullValue()))));
|
||||
.andExpect(jsonPath("_links.tags", is(notNullValue()))));
|
||||
}
|
||||
|
||||
void getTags(String taggedNoteLocation) throws Exception {
|
||||
@@ -182,7 +178,7 @@ public class GettingStartedDocumentation {
|
||||
.andReturn(), "tags");
|
||||
document("get-tags",
|
||||
this.mockMvc.perform(get(tagsLocation)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("links[?(@.rel==tag)]", is(notNullValue()))));
|
||||
.andExpect(jsonPath("_embedded.tags", hasSize(1))));
|
||||
}
|
||||
|
||||
void tagExistingNote(String noteLocation, String tagLocation) throws Exception {
|
||||
@@ -191,12 +187,10 @@ public class GettingStartedDocumentation {
|
||||
|
||||
document(
|
||||
"tag-existing-note",
|
||||
this.mockMvc
|
||||
.perform(
|
||||
patch(noteLocation).contentType(
|
||||
MediaType.APPLICATION_JSON).content(
|
||||
objectMapper.writeValueAsString(update)))
|
||||
.andDo(print()).andExpect(status().isNoContent()));
|
||||
this.mockMvc.perform(
|
||||
patch(noteLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||
objectMapper.writeValueAsString(update))).andExpect(
|
||||
status().isNoContent()));
|
||||
|
||||
}
|
||||
|
||||
@@ -210,12 +204,12 @@ public class GettingStartedDocumentation {
|
||||
.andReturn(), "tags");
|
||||
document("get-tags-for-existing-note",
|
||||
this.mockMvc.perform(get(tagsLocation)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("links[?(@.rel==tag)]", is(notNullValue()))));
|
||||
.andExpect(jsonPath("_embedded.tags", hasSize(1))));
|
||||
}
|
||||
|
||||
private String getLink(MvcResult result, String rel)
|
||||
private String getLink(MvcResult result, String href)
|
||||
throws UnsupportedEncodingException {
|
||||
return JsonPath.parse(result.getResponse().getContentAsString()).read(
|
||||
"links[?(@.rel==" + rel + ")][0].href");
|
||||
"_links.tags.href");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.notes;
|
||||
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.Resources;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
|
||||
public class NestedContentResource<T> extends ResourceSupport {
|
||||
|
||||
private final Resources<T> nested;
|
||||
|
||||
public NestedContentResource(Iterable<T> toNest) {
|
||||
this.nested = new Resources<T>(toNest);
|
||||
}
|
||||
|
||||
@JsonUnwrapped
|
||||
public Resources<T> getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
import com.example.notes.NoteResourceAssembler.NoteResource;
|
||||
import com.example.notes.TagResourceAssembler.TagResource;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/notes")
|
||||
@@ -47,17 +48,21 @@ public class NotesController {
|
||||
|
||||
private final NoteResourceAssembler noteResourceAssembler;
|
||||
|
||||
private final TagResourceAssembler tagResourceAssembler;
|
||||
|
||||
@Autowired
|
||||
public NotesController(NoteRepository noteRepository, TagRepository tagRepository,
|
||||
NoteResourceAssembler noteResourceAssembler) {
|
||||
NoteResourceAssembler noteResourceAssembler, TagResourceAssembler tagResourceAssembler) {
|
||||
this.noteRepository = noteRepository;
|
||||
this.tagRepository = tagRepository;
|
||||
this.noteResourceAssembler = noteResourceAssembler;
|
||||
this.tagResourceAssembler = tagResourceAssembler;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
Iterable<NoteResource> all() {
|
||||
return this.noteResourceAssembler.toResources(this.noteRepository.findAll());
|
||||
NestedContentResource<NoteResource> all() {
|
||||
return new NestedContentResource<NoteResource>(
|
||||
this.noteResourceAssembler.toResources(this.noteRepository.findAll()));
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
@@ -85,12 +90,9 @@ public class NotesController {
|
||||
|
||||
@RequestMapping(value = "/{id}/tags", method = RequestMethod.GET)
|
||||
ResourceSupport noteTags(@PathVariable("id") long id) {
|
||||
ResourceSupport resource = new ResourceSupport();
|
||||
Note note = this.noteRepository.findOne(id);
|
||||
for (Tag tag : note.getTags()) {
|
||||
resource.add(linkTo(TagsController.class).slash(tag.getId()).withRel("tag"));
|
||||
}
|
||||
return resource;
|
||||
return new NestedContentResource<TagResource>(
|
||||
this.tagResourceAssembler.toResources(this.noteRepository.findOne(id)
|
||||
.getTags()));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.PATCH)
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.example.notes.NoteResourceAssembler.NoteResource;
|
||||
import com.example.notes.TagResourceAssembler.TagResource;
|
||||
|
||||
@RestController
|
||||
@@ -38,17 +39,23 @@ public class TagsController {
|
||||
|
||||
private final TagRepository repository;
|
||||
|
||||
private final TagResourceAssembler resourceAssembler;
|
||||
private final NoteResourceAssembler noteResourceAssembler;
|
||||
|
||||
private final TagResourceAssembler tagResourceAssembler;
|
||||
|
||||
@Autowired
|
||||
public TagsController(TagRepository repository, TagResourceAssembler resourceAssembler) {
|
||||
public TagsController(TagRepository repository,
|
||||
NoteResourceAssembler noteResourceAssembler,
|
||||
TagResourceAssembler tagResourceAssembler) {
|
||||
this.repository = repository;
|
||||
this.resourceAssembler = resourceAssembler;
|
||||
this.noteResourceAssembler = noteResourceAssembler;
|
||||
this.tagResourceAssembler = tagResourceAssembler;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
Iterable<TagResource> all() {
|
||||
return this.resourceAssembler.toResources(this.repository.findAll());
|
||||
NestedContentResource<TagResource> all() {
|
||||
return new NestedContentResource<TagResource>(
|
||||
this.tagResourceAssembler.toResources(this.repository.findAll()));
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
@@ -68,17 +75,13 @@ public class TagsController {
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
|
||||
Resource<Tag> tag(@PathVariable("id") long id) {
|
||||
Tag tag = this.repository.findOne(id);
|
||||
return this.resourceAssembler.toResource(tag);
|
||||
return this.tagResourceAssembler.toResource(tag);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/{id}/notes", method = RequestMethod.GET)
|
||||
ResourceSupport tagNotes(@PathVariable("id") long id) {
|
||||
ResourceSupport resource = new ResourceSupport();
|
||||
Tag tag = this.repository.findOne(id);
|
||||
for (Note note : tag.getNotes()) {
|
||||
resource.add(linkTo(NotesController.class).slash(note.getId())
|
||||
.withRel("note"));
|
||||
}
|
||||
return resource;
|
||||
return new NestedContentResource<NoteResource>(
|
||||
this.noteResourceAssembler.toResources(this.repository.findOne(id)
|
||||
.getNotes()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user