Representing a request body on HATEOAS link - java

I have a simple question regarding how do I link an endpoint through HATEOAS if that endpoint requires a valid #RequestBody? I've seen another question regarding this but with no straight answers and so I wonder if the question was not clear enough.
Take a look at the code below:
#GetMapping(path = "/notification/{id}")
#ResponseStatus(HttpStatus.OK)
public NotificationItemResponse getNotification(#PathVariable final String id) {
return notificationItemMapper.toResponse(findUseCase.findNotification(id))
.add(linkTo(methodOn(NotificationItemController.class).getNotification(id)).withSelfRel())
.add(linkTo(methodOn(NotificationItemController.class).saveNotification()).withRel("save")) <- error here, saveNotification expects a valid request body
.add(linkTo(methodOn(NotificationItemController.class).revokeNotification(id)).withRel("revoke"))
.add(linkTo(methodOn(NotificationItemController.class).markNotificationAsSeen(id, "{userName}")).withRel("visualize"));
}
saveNotification() is a method on this controller that requires a request body. That request body is a long json containing a notification message, target users, etc.
It doesn't seem right to create a dummy body just to pass down here, and it hardly seem right to pass a null value. What is the correct approach here? How do I correctly link a method that requires a request body? More specifically, what do I pass down as that request body?
What is the best practice here, other than passing a null or dummy body, as I stated before as a non-optimal solution?

The question is pretty old, but I faced the same issue today, and it looks pretty hard to find the correct answer.
After some research, I found this example in Spring HATEOAS Docs: 3.3 Affordances
#GetMapping("/employees/{id}")
public EntityModel<Employee> findOne(#PathVariable Integer id) {
Class<EmployeeController> controllerClass = EmployeeController.class;
// Start the affordance with the "self" link, i.e. this method.
Link findOneLink = linkTo(methodOn(controllerClass).findOne(id)).withSelfRel();
// Return the affordance + a link back to the entire collection resource.
return EntityModel.of(EMPLOYEES.get(id), //
findOneLink //
.andAffordance(afford(methodOn(controllerClass).updateEmployee(null, id)))
.andAffordance(afford(methodOn(controllerClass).partiallyUpdateEmployee(null, id))));
}
In this case, they use a method afford(...), which works pretty similar to linkTo(...). Looks like passing a null object is a best practice, or at least it is encouraged by the Spring team. So in your case it would look like this:
.add(linkTo(methodOn(NotificationItemController.class).saveNotification(null)).withRel("save"))

Related

RestController design struggle - Spring Boot REST API

I'm quite new into REST API development. I have decided to create a Blog application using Spring Boot and I'm really struggling with the design and structure of my app.
Right now my app consists of Post and Comment models and repositories. For both models, I have created service classes (PostService and CommentService). In these classes, I have all the business logic (just simple CRUD right now).
Now I am scratching my head about the design of my #RestControler for Posts. In PostController I have exposed these actions:
#PostMapping("/api/posts/create")
public Post create(#RequestBody Post post) { ... }
#GetMapping("/api/posts")
public List<Post> findAll() { ... }
#GetMapping("/api/posts/{id}")
public Post findById(#PathVariable("id") Long id) { ... }
#PutMapping("/api/posts/{id}")
public Post update(#RequestBody Post post) { ... }
#DeleteMapping("/api/posts/{id}")
public void delete(#PathVariable Long id) { ... }
Now I'm getting to my question. I am wondering what is correct design of adding a Comment to the Post.
Should I expose all CRUD method for Comment using CommentController class and use create method?
Is it ok to add a new method addComment to PostController which will create a new Comment?
In my head adding a Comment to the Post belongs to the Post, but I really don't know.
Could some of give me some advice regarding this matter?
Thanks a lot!
Bye,
Tom
If I were you, I'd consider REST Design Principles from the OpenAPI Specification and would follow resource -> sub-resource -> method||identifier pattern. This would probably be the most KISS and clean design for the readability and understanding purposes.
#PostMapping("/api/posts/") //you don't need /create as a separate URI
public Post create(#RequestBody Post post) { ... }
#GetMapping("/api/posts") //This is OK.
public List<Post> findAll() { ... }
#GetMapping("/api/posts/{id}") //OK, however {id} should be optional, hence you can combine this and upper methods in one method.
public Post findById(#PathVariable("id") Long id) { ... }
#PutMapping("/api/posts/{id}") //OK.
public Post update(#RequestBody Post post) { ... }
#DeleteMapping("/api/posts/{id}") //OK.
public void delete(#PathVariable Long id) { ... }
and now, for the comments API design, I would have contain them under posts resource, and would have added these corresponding URIs:
#GetMapping("/api/posts/{id}/comments/{commendId}") //commentId is optional
#PostMapping("/api/posts/{id}/comments/") //you don't need any {commendId} here, just post the payload
and etc. I hope you can come up with method signatures and other method mappings.
You can also see the RESTful naming conventions here
To be honest I don't think that someone can give you the perfect answer here. It is often a personal decision. In common you can say the following about a REST API.
the path should only represent your data structure in the database. So for example /api/posts
No verbs in your path. What you want to do should be handled by the RequestType (GET, POST, PUT, PATCH, DELETE, etc.)
Now to your case. I can really good understand why you are struggling. I think here are two options:
PostsController
You say a Comment is always a part of a Post and because of this
you design your API like this.
#PostMapping("/api/posts/{id}/comment")
public Comment create(#PathVariable Long id), #RequestBody Comment comment) { ... }
CommentsController
You handle Comment as an own object and the Post is just a relation you add to it by attribute.
#PostMapping("/api/comments")
public Comment create(#RequestBody Comment comment) { ... }
So it is always is it a Subset vs make own Object structure. I think in this case here I would prefer option 2 because I think you want do more operations on this object.
Also you can than design your API in the way that every Controller starts with the object that will be handled /api/OBJECT/xxx/yyy
UPDATE
After reading the comment from #gulliva I think also a good way is to use this URL #PostMapping("/api/posts/{id}/comment") but put it in the CommentsController. I think this is a good way.

Handle Resource not found in Rest API

I am developing a Rest API in spring boot. Which of the following is the best way to handle when an instance of resource not found ?
#GetMapping(value="/book/{id}")
public ResponseEntity<Book> getBook(#PathVariable String id){
Book book = bookService.getBook();
// Which is best Approach for resource instance not found ?
if(book == null) {
// This one
return new ResponseEntity<>(book, HttpStatus.NO_CONTENT);
//OR
return new ResponseEntity<>(book, HttpStatus.NOT_FOUND);
//OR
throw new DataNotFoundException("Book with id " + id + " Does not exist");
}
return new ResponseEntity<>(book , HttpStatus.OK);
}
I am clear about that when a collection of resource not found in Db then to pass an empty collection instead of null but I am not clear what to do with an instance of resource.
I have also read on StackOverflow that HttpStatus.NOT_FOUND should be used when a Resource under the criteria cannot exist instead of do not exist in the Db.
What is best approach to handle this ?
When working with Spring MVC, you usually have two choices when returning your result, either you work with plain objects, or you work with the ResponseEntity class. Neither of those is better than the other. Additionally, you can decide whether or not you separate your error handling using exceptions or not.
Given that, your third scenario by throwing an exception is essentially the same as one of your first two options. By default, throwing an exception will result into a 500 Internal Server Error, but it can be changed by using the #ResponseStatus annotation, for example:
#ResponseStatus(HttpStatus.NOT_FOUND) // Or #ResponseStatus(HttpStatus.NO_CONTENT)
public class DataNotFoundException extends RuntimeException {
}
Alternatively, you can also define an exception handler. Again, this can be done by either working with plain objects or ResponseEntity, for example:
#ResponseStatus(HttpStatus.NOT_FOUND) // Or #ResponseStatus(HttpStatus.NO_CONTENT)
#ExceptionHandler(DataNotFoundException.class)
public Book handleNotFound(DataNotFoundException ex) {
return null;
}
Or:
#ExceptionHandler(DataNotFoundException.class)
public ResponseEntity<Book> handleNotFound(DataNotFoundException ex) {
return new ResponseEntity<>(null, HttpStatus.NOT_FOUND); // Or HttpStatus.NO_CONTENT
}
Again, neither is better than the other and what you choose is mostly based upon personal preference. However, you should probably use one consistently.
Now, that means that there are still two choices left, either by choosing HttpStatus.NOT_FOUND (404) or HttpStatus.NO_CONTENT (204). While you can technically use either status, they have a different meaning:
204 = The request was succesful, but there's nothing.
404 = The request was not succesful, the resource does not exist
Now, if you request /book/123 and there's no book with ID 123, it could be seen as a resource that doesn't exist, and thus, HttpStatus.NOT_FOUND makes most sense.
First of all I think that you mean #PathVariable and not #RequestParam for your method parameter (see difference between PathVariable and RequestParam here ).
Secondly, it will be ambiguous for the client that receives the 404 not found response as this means that :
The server has not found anything matching the requested address (URI)
( not found ). This means the URL you have typed is wrong or obsolete
and does not match any document existing on the server (you may try to
gradualy remove the URL components from the right to the left to
eventualy retrieve an existing path).
Knowing that your return type is a ResponsEntity, it will be more appropriate to have this :
#GetMapping(value="/book/{id}")
public ResponseEntity getBook(#PathVariable String id){
Optional<Book> book = bookService.getBook();
if(book.isPresent()) {
return ResponseEntity.status(HttpStatus.OK).body(book.get());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
If your endpoint gets book by id and the book does not exists, return 400. Do not return 404. 404 is protocol error: it should be reserved for wrong URL. Now URL is correct but id is wrong. Id almost often is not guessed, but returned by previous query. It cannot disappear suddenly: if id is wrong, the request is wrong.
If your endpoint gets book by title and the book does not exists, return 204. That is absolutely normal that book does not exists in such case and client should be prepared to handle 204.
Someone could argue that difference between 400 and 204 is fuzzy and better always return 204. Indeed, difference may be fuzzy, but from monitoring perspective I would like to know when everything is ok (no book found by title) and when something smells (no book found by id).
I know that my answer does not comply REST directives (or maybe does not comply). I don't care it too much. I simply think that 404 should be reserved for application server and should not be used by application. Reason is already explained in other answer here.
Summary:
404: wrong URL
400: wrong id
204: not found and that is OK
just return 404 HttpStatus to client ,do not waste time on it.No one will request id that not exist in db normally. usually client request like model/{id} come from
against your Collection [model1,model2,.....]
Whenever a resource cannot be found, you should indicate that to the client, most commonly using the HTTP Status Code 404 Not Found, as you already mentioned.
For collections, simply return an empty array in the response body (alongside with response code 200 OK, this is my opinion tough), do not return 404 Not Found since the resource actually exists.
Please note that 202 No Content is a bad choice here, since the server has not successfully fulfilled the request. Instead, use this return code, for example, for a successful PUT request (you have changed internal data but return no content in the response body).
In most APIs you will encounter additional information in the response body:
{"messages":{"error":[{"code":404,"message":"Resource not found."}]}}
You will find list of all errors and their response codes with informative descriptions. One thing is important tough: Stick to one format, otherwise it will be a pain for clients. Most APIs also only use about 6-8 HTTP response codes.
Also, Spring has a number of utilities to help you out:
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order")
public class OrderNotFoundException extends RuntimeException {
// ...
}
Or, the following annotation to create a custom response format:
#ExceptionHandler({ YourException.class })

Does Jersey 2.x API have one function that can execute flexible REST verbs (GET, POST)

I'm in the middle of upgrading a bunch of test harness Groovy(Java) to execute against the Jersey 2.x Client (javax.ws.rs.client.Client), upgrading it from 1.x. I'll likely stumble upon the feature soon but can't see if for the changes I'm making right now. Have been poring through the API looking for an answer to my question.
The new API seems to only permit strict fluent interface verbs e.g.
Response response = invocationBuilder.get();
The old 1.x code allowed a parameter that defines the method type using a string parameter called method:
def response = userServiceContext.target()
.path("/$path")
.method(method, ClientResponse.class)
Annoyingly strict new code displaying a 'put' call (examples):
WebTarget resourceTarget = jerseyClient().target("/$path")
Invocation invocation = resourceTarget.request(MediaType.APPLICATION_ATOM_XML)
.accept(MediaType.APPLICATION_ATOM_XML)
.put(ClientResponse.class, entry)
return invocation.submit()
For convenience sake [asking too much? :) ] could someone point me to an out of the box method that does the same thing in 2.x? I've been digging around the API and I'm finding this an annoying pebble. Bonus points if you explain why that flexibility doesn't exist in the new API (without sass). Apologies in advance to questions like what have you done or why don't you try it and see!??
It looks like you can do something like resourceTarget.request().build(method)... to do what you are looking for. Check out the docs for the build(String method) and build(String method, Entity<?> entity) methods here.

Jersey reuse parameter validation for subresources

I have to following endpoint structure in Jersey:
/objects/
/objects/:id
/objects/:id/:field
/objects/:id/:field/:subfield
The IDs I'm using have a very specific format, so I first check if the format is valid before making a request to the database.
Right now I have to put this code in each of the POST, PUT, GET, DELETE functions for each of the functions that has :id as a parameter. So this just means an early return.
if (!isIdValid(id)){
return Response.status(Response.StatusType.BAD_REQUEST)
.entity("The ID you've provided is invalid")
.build();
}
(In reality the error entity is an object containing more information about the error)
And then for each function using the :field or :subfield parameters the code is similar. This checking and error-handling behavior has to be copied every time. And when I start copy-pasting stuff, I start thinking: there should be a better way?
I would like to place the :id checking code at the the /objects/:id level, and then all further nested levels are assumed have a valid ID. The same for the other parameters further nesting down.
I've been looking into using subresource locators, but then you create a function returning a new instance of the subresource. I can't put a conditional return of a Response-object at that level for if the validation fails.
#Path("{id}")
function Class<ObjectFieldResource> getObjectById(#PathParam("id") String id){
return ObjectFieldResource.class;
}
I could start throwing exceptions, but I would rather avoid that, since I don't really consider invalid input to be an exception.
How would such a structure best be implemented? I've looked at bean validation but that doesn't seem to allow me to define validation for my specific format + custom error responses.
Am I missing something in the way subresources should be implemented?
Solution 1
If you can use regexp checks instead of your isIdValid method it's possible to define your resources like this
#POST
#Path("objects/{id:\\d+}")
public Response doSmth(#PathParam("id") String id) {
...
}
In a case of invalid id format caller will have 'Not Found' response status without even reaching your doSmth method.
Obviously, you can use String constants for all equal path values.
final static String ID_RES = "objects/{id:\\d+}";
#POST
#Path(ID_RES)
public Response postSmth(#PathParam("id") String id) {
...
}
...
#GET
#Path(ID_RES)
public Object getSmth(#PathParam("id") String id) {
...
}
The can also read full description of Path#value parameter
Solution 2
Create and register at your REST server javax.ws.rs.container.ContainerRequestFilter implementation with filter method having needed URI checks.
The single filter parameter has ContainerRequestContext type from witch you can call getUriInfo for getting URI and method abortWith(Response response) which can be used for aborting caller request if your resource ids validation was failed.
See Chapter 10. Filters and Interceptors chapter of Jersey Manual.

Is it possible to get a #PathParam or #QueryParam from the MessageBodyReaderContext in a RestEASY MessageBodyReaderInterceptor?

My service:
#POST
public String setData(#QueryParam("id") Long is, MyObject payload) {
...
}
or
#POST
public String setData(#PathParam("id") Long is, MyObject payload) {
...
}
My interceptor on the server:
Object read(MessageBodyReaderContext context) throws IOException, WebApplicationException {
Class mypayloadtype = context.getType;
InputStream mypayloadinpustream = context.getInputStream();
Long myidparam = ???????? // how to get the query or path param here?
}
EDIT: To be a bit more concrete:
What I'd like to do is to grab the XML and store it based on the parameters in a separate audit system. Maybe PreProcessInterceptor / PostProcessInterceptor are the better choices?
Any hints or alternative ways to get the param when the xml is still available for preprocessing?
Miguel
I just stumbled over the same problem today. I needed the #PathParams and #QueryParams in the read() method and ended up with something like this:
public class MyInterceptor implements PreProcessInterceptor, MessageBodyReaderInterceptor
{
private static ThreadLocal<UriInfo> uri = new ThreadLocal<UriInfo>();
public ServerResponse preProcess(HttpRequest request, ResourceMethod method)
{
uri.set(request.getUri);
...
}
public Object read(MessageBodyReaderContext context)
{
String param = uri.get().getPathParameters().getFirst("myidparam");
...
}
}
Although when thinking about it now - I'm not quite sure, if just using PreProcessInterceptor/PostProcessInterceptor will also do the trick for my (and maybe your) problem. I'll have another look tomorrow.
I am not an expert on the topic but to me it seems as if the MessageBodyReaderContext interface does not really know if it is on the server or the client side, so it cannot expose the request or its parameters / path parts etc.
So as far as I know this is not possible.
If your code knows that it lives on the server side of the rest
communication, maybe you can use a servlet filter to store the request
in a ThreadLocal and then access it from there while the request is
handled, somewhat similar to RequestContextFilter / RequestContextHolder from the spring framework? (Then the request object does not know anything about the annotations of your service, but instead one has to extract the information manually from the request. This means to have the same information in two places, so there has to be a better solution ...)
Edit: after looking at some examples I get the vague feeling that if you want to read the input stream to create an object and add path parameters to it, MessageBodyReaderInterceptor is simply not the way to go. Instead set up a MessageBodyReader which constructs the object from the request body data, and this then will be passed into the public String setData(#PathParam("id") Long is, MyObject payload), assuming that this method is annotated with a #Consumes which matches the #ConsumeMime annotation for the MessageBodyReader. There you might be able in the setData to set the missing id on the object read from the request body. Some examples related to this seem to be here: How to get full REST request body using Jersey? (but for Jersey, not jBoss :-/)
However I am not sure if that works for you, and I also feel I completely overestimated my ability to answer this question appropriately, so I hope someone more knowledgeable comes in with a better solution.

Categories

Resources