Improve error handling in the Spring HATEOAS-based sample

This commit is contained in:
Andy Wilkinson
2014-11-04 10:32:55 +00:00
parent 4cb6536d61
commit adddb1471a
7 changed files with 143 additions and 10 deletions

View File

@@ -0,0 +1,39 @@
/*
* 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 java.util.Map;
import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
@Component
class ExceptionSupressingErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
errorAttributes.remove("exception");
Object message = requestAttributes.getAttribute("javax.servlet.error.message", RequestAttributes.SCOPE_REQUEST);
if (message != null) {
errorAttributes.put("message", message);
}
return errorAttributes;
}
}

View File

@@ -18,10 +18,13 @@ package com.example.notes;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.springframework.data.repository.CrudRepository;
public interface NoteRepository extends CrudRepository<Note, Long> {
Optional<Note> findById(long id);
List<Note> findByTagsIn(Collection<Tag> tags);
}

View File

@@ -42,6 +42,8 @@ import com.example.notes.TagResourceAssembler.TagResource;
@RequestMapping("/notes")
public class NotesController {
private static final UriTemplate TAG_URI_TEMPLATE = new UriTemplate("/tags/{id}");
private final NoteRepository noteRepository;
private final TagRepository tagRepository;
@@ -52,7 +54,8 @@ public class NotesController {
@Autowired
public NotesController(NoteRepository noteRepository, TagRepository tagRepository,
NoteResourceAssembler noteResourceAssembler, TagResourceAssembler tagResourceAssembler) {
NoteResourceAssembler noteResourceAssembler,
TagResourceAssembler tagResourceAssembler) {
this.noteRepository = noteRepository;
this.tagRepository = tagRepository;
this.noteResourceAssembler = noteResourceAssembler;
@@ -84,21 +87,25 @@ public class NotesController {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
Resource<Note> note(@PathVariable("id") long id) {
Note note = this.noteRepository.findOne(id);
Note note = this.noteRepository.findById(id).orElseThrow(
() -> new ResourceDoesNotExistException());
return this.noteResourceAssembler.toResource(note);
}
@RequestMapping(value = "/{id}/tags", method = RequestMethod.GET)
ResourceSupport noteTags(@PathVariable("id") long id) {
return new NestedContentResource<TagResource>(
this.tagResourceAssembler.toResources(this.noteRepository.findOne(id)
.getTags()));
this.tagResourceAssembler
.toResources(this.noteRepository.findById(id)
.orElseThrow(() -> new ResourceDoesNotExistException())
.getTags()));
}
@RequestMapping(value = "/{id}", method = RequestMethod.PATCH)
@ResponseStatus(HttpStatus.NO_CONTENT)
void updateNote(@PathVariable("id") long id, @RequestBody NotePatchInput noteInput) {
Note note = this.noteRepository.findOne(id);
Note note = this.noteRepository.findById(id).orElseThrow(
() -> new ResourceDoesNotExistException());
if (noteInput.getTagUris() != null) {
note.setTags(getTags(noteInput.getTagUris()));
}
@@ -112,11 +119,23 @@ public class NotesController {
}
private List<Tag> getTags(List<URI> tagLocations) {
UriTemplate template = new UriTemplate("/tags/{id}");
return tagLocations
.stream()
.map(location -> this.tagRepository.findOne(Long.valueOf(template.match(
location.toASCIIString()).get("id"))))
.map(location -> this.tagRepository.findById(extractTagId(location))
.orElseThrow(
() -> new IllegalArgumentException("The tag '" + location
+ "' does not exist")))
.collect(Collectors.toList());
}
private long extractTagId(URI tagLocation) {
try {
String idString = TAG_URI_TEMPLATE.match(tagLocation.toASCIIString()).get(
"id");
return Long.valueOf(idString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException("The tag '" + tagLocation + "' is invalid");
}
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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;
@SuppressWarnings("serial")
public class ResourceDoesNotExistException extends RuntimeException {
}

View File

@@ -0,0 +1,44 @@
/*
* 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 java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class RestNotesControllerAdvice {
@ExceptionHandler(IllegalArgumentException.class)
public void handleIllegalArgumentException(IllegalArgumentException ex,
HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value(), ex.getMessage());
}
@ExceptionHandler(ResourceDoesNotExistException.class)
public void handleResourceDoesNotExistException(ResourceDoesNotExistException ex,
HttpServletRequest request, HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.NOT_FOUND.value(),
"The resource '" + request.getRequestURI() + "' does not exist");
}
}

View File

@@ -16,8 +16,12 @@
package com.example.notes;
import java.util.Optional;
import org.springframework.data.repository.CrudRepository;
public interface TagRepository extends CrudRepository<Tag, Long> {
Optional<Tag> findById(long id);
}

View File

@@ -74,14 +74,16 @@ public class TagsController {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
Resource<Tag> tag(@PathVariable("id") long id) {
Tag tag = this.repository.findOne(id);
Tag tag = this.repository.findById(id).orElseThrow(
() -> new ResourceDoesNotExistException());
return this.tagResourceAssembler.toResource(tag);
}
@RequestMapping(value = "/{id}/notes", method = RequestMethod.GET)
ResourceSupport tagNotes(@PathVariable("id") long id) {
return new NestedContentResource<NoteResource>(
this.noteResourceAssembler.toResources(this.repository.findOne(id)
this.noteResourceAssembler.toResources(this.repository.findById(id)
.orElseThrow(() -> new ResourceDoesNotExistException())
.getNotes()));
}
}