diff --git a/rest-notes-spring-hateoas/src/main/java/com/example/notes/ExceptionSupressingErrorAttributes.java b/rest-notes-spring-hateoas/src/main/java/com/example/notes/ExceptionSupressingErrorAttributes.java new file mode 100644 index 00000000..e8abbe59 --- /dev/null +++ b/rest-notes-spring-hateoas/src/main/java/com/example/notes/ExceptionSupressingErrorAttributes.java @@ -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 getErrorAttributes(RequestAttributes requestAttributes, + boolean includeStackTrace) { + Map 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; + } +} diff --git a/rest-notes-spring-hateoas/src/main/java/com/example/notes/NoteRepository.java b/rest-notes-spring-hateoas/src/main/java/com/example/notes/NoteRepository.java index 8185ffa7..01e41b57 100644 --- a/rest-notes-spring-hateoas/src/main/java/com/example/notes/NoteRepository.java +++ b/rest-notes-spring-hateoas/src/main/java/com/example/notes/NoteRepository.java @@ -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 { + Optional findById(long id); + List findByTagsIn(Collection tags); } diff --git a/rest-notes-spring-hateoas/src/main/java/com/example/notes/NotesController.java b/rest-notes-spring-hateoas/src/main/java/com/example/notes/NotesController.java index cb9f655d..033ecd2f 100644 --- a/rest-notes-spring-hateoas/src/main/java/com/example/notes/NotesController.java +++ b/rest-notes-spring-hateoas/src/main/java/com/example/notes/NotesController.java @@ -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(@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( - 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 getTags(List 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"); + } + } } diff --git a/rest-notes-spring-hateoas/src/main/java/com/example/notes/ResourceDoesNotExistException.java b/rest-notes-spring-hateoas/src/main/java/com/example/notes/ResourceDoesNotExistException.java new file mode 100644 index 00000000..a3de9e54 --- /dev/null +++ b/rest-notes-spring-hateoas/src/main/java/com/example/notes/ResourceDoesNotExistException.java @@ -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 { + +} diff --git a/rest-notes-spring-hateoas/src/main/java/com/example/notes/RestNotesControllerAdvice.java b/rest-notes-spring-hateoas/src/main/java/com/example/notes/RestNotesControllerAdvice.java new file mode 100644 index 00000000..a8b1c0f7 --- /dev/null +++ b/rest-notes-spring-hateoas/src/main/java/com/example/notes/RestNotesControllerAdvice.java @@ -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"); + } + +} diff --git a/rest-notes-spring-hateoas/src/main/java/com/example/notes/TagRepository.java b/rest-notes-spring-hateoas/src/main/java/com/example/notes/TagRepository.java index 8790619b..97fbffcb 100644 --- a/rest-notes-spring-hateoas/src/main/java/com/example/notes/TagRepository.java +++ b/rest-notes-spring-hateoas/src/main/java/com/example/notes/TagRepository.java @@ -16,8 +16,12 @@ package com.example.notes; +import java.util.Optional; + import org.springframework.data.repository.CrudRepository; public interface TagRepository extends CrudRepository { + Optional findById(long id); + } diff --git a/rest-notes-spring-hateoas/src/main/java/com/example/notes/TagsController.java b/rest-notes-spring-hateoas/src/main/java/com/example/notes/TagsController.java index 891e90fa..8af0dca2 100644 --- a/rest-notes-spring-hateoas/src/main/java/com/example/notes/TagsController.java +++ b/rest-notes-spring-hateoas/src/main/java/com/example/notes/TagsController.java @@ -74,14 +74,16 @@ public class TagsController { @RequestMapping(value = "/{id}", method = RequestMethod.GET) Resource 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( - this.noteResourceAssembler.toResources(this.repository.findOne(id) + this.noteResourceAssembler.toResources(this.repository.findById(id) + .orElseThrow(() -> new ResourceDoesNotExistException()) .getNotes())); } }