20

I'm trying to build messanger app.

I've to call CommentResource from MessageResource.

I want separate MessageResources and CommentResources.

I'm doing something like this :

MessageResource.java

@RestController
@RequestMapping("/messages")
public class MessageResource {

    MessageService messageService = new MessageService();

    @RequestMapping(value = "/{messageId}/comments")
    public CommentResource getCommentResource() {
        return new CommentResource();
    }
}

CommentResource.java

@RestController
@RequestMapping("/")
public class CommentResource {

    private CommentService commentService = new CommentService();

    @RequestMapping(method = RequestMethod.GET, value="/abc")
    public String test2() {
        return "this is test comment";
    }
}

I want

http://localhost:8080/messages/1/comments/abc

to return "this is test comment".

Any Idea??

PS: In a simple word, I want to know JAX-RS sub-resource equivalent implementation in spring-rest

Govinda Sakhare
  • 5,009
  • 6
  • 33
  • 74
prranay
  • 1,789
  • 5
  • 23
  • 33
  • Why are you trying to return a controller from another controller method instead of some sort of value? – chrylis -cautiouslyoptimistic- Oct 30 '16 at 12:12
  • And since you want this resource to have the path /messages/1/comments/abc, why do you map it to / instead of /messages/{messageId}/comments? – JB Nizet Oct 30 '16 at 12:31
  • I want to build CommentResource as sub-resource of MessageResource and keep Comment APIs in CommentResource. With "/{messageId}/comments" in MessageResource i want to redirect all requests to CommentResource. – prranay Oct 30 '16 at 14:35
  • If I put /messages/{messageId}/comments/abc in my MessageResource class, it works. But i want to keep it separate as /abc is related to comments and not Message. – prranay Oct 30 '16 at 14:38
  • 3
    @prranay Did you find any equivalent way to do with Spring? Those commenters don't understand what you're trying to do, apparently. – Jin Kwon Oct 31 '17 at 08:25

7 Answers7

20

In Spring boot, we can implement JAX-RS subresource concept using @Autowired Spring Concept. Create an object of your child resource class and Spring will initialize at runtime and return that object. Don't create an Object manually. like: Above mention scenario

 - MessageResource.java

@RestController
@RequestMapping("/messages")
public class MessageResource {

    MessageService messageService = new MessageService();
    @Autowired
    @Qualifier("comment")
    CommentResource comment;

    @RequestMapping(value = "/{messageId}/comments")
    public CommentResource getCommentResource() {
        return comment;
    }
}    

 - CommentResource.java

@RestController("comment")
@RequestMapping("/")
public class CommentResource {

    private CommentService commentService = new CommentService();

    @RequestMapping(method = RequestMethod.GET, value="/abc")
    public String test2() {
        return "this is test comment";
    }
}



Now it will work like sub-resource
http://localhost:8080/messages/1/comments/abc

You can send any type of request.
Tapan
  • 285
  • 2
  • 8
  • Works like a charm! Sad that I couldn't find an example like this in the official documentation. – spaghettifunk Sep 22 '19 at 08:08
  • 3
    How were you able to run it? Does it require some configuration? At the current version of spring boot (2.2.4.RELEASE) it treats CommentResource as response value to serialize. If I add a field with getter I get json representation of it. And on calling GET http://localhost:8080/messages/1/comments/abc I get 404. – Mariusz Zawadzki Mar 04 '20 at 09:33
  • 4
    Man I'm not sure how to make sense out of this. First off, the "public CommentResource getCommentResource()" returns the CommentResource but doesn't inject the messageId context into it or use it in any way.. so I'm not sure if this is a mistake in the answer or my knowledge gap - clarification would be appreciated. Also the Comment resource has a different path "comment" as opposed to "comments" - eg singular vs. plural so I'm not sure if this is intentional - I think it is a mistake. Thanks for any correction or response. – TheJeff Aug 24 '20 at 15:35
7

Your url (http://localhost:8080/messages/1/comments/abc) suggests that comments are nested in the message. Your controller should look like this:

@RestController
@RequestMapping("/messages")
public class MessageResource {

    @RequestMapping(value = "/{messageId}")
    public String getCommentResource(@PathVariable("messageId") String messageId) {
        //test
        return messageId;
    }

    @RequestMapping(value = "/{messageId}/comments/{commentsContent}")
    public String getCommentResource(
                      @PathVariable("messageId") String messageId, 
                      @PathVariable("commentsContent") String commentsContent) {
        //test
        return messageId + "/" + commentsContent;
    }
}

I'm not entirely sure what you wish to do in your MessageResource class, but the idea is there.

Rest - HTTP methods

For now, these use are Get requests. You should however consider using the appropriate Http Method:

  • Get: read a resource
  • Post: create a resource
  • Put: update
  • Delete: delete :)

Take a look at this: http://www.restapitutorial.com/lessons/httpmethods.html

Example with Post:

@RequestMapping(method=RequestMethod.POST, value = "/{messageId}/comments/{commentsContent}")
    public ResponseEntity<String> getCommentResource(
                                  @PathVariable("messageId") String messageId, 
                                  @RequestBody Comment comment) {
        //fetch the message associated with messageId
        //add the comment to the message
        //return success
        return new ResponseEntity<String>(HttpStatus.OK);
 }

Class names

Also, I would personally rename these classes to MessageController and CommentController.

Edit after comments - Split controllers

You can just literally split the controllers (closer to what you had):

@RestController
@RequestMapping("/messages")
public class MessageResource {

    @RequestMapping(value = "/{messageId}")
    public String getCommentResource(@PathVariable("messageId") String messageId) {
        //test
        return messageId;
    }
}

@RestController
@RequestMapping("/messages")
public class CommentResource {

    @RequestMapping(value = "/{messageId}/comments/{commentsContent}")
    public String getCommentResource(
                      @PathVariable("messageId") String messageId, 
                      @PathVariable("commentsContent") String commentsContent) {
        //test
        return messageId + "/" + commentsContent;
    }
}
alexbt
  • 16,415
  • 6
  • 78
  • 87
  • Your solution works perfectly but I want to keep these two controllers separate and don't want to combine. – prranay Oct 30 '16 at 14:13
  • yeah it solves he problem and code looks clean As MessageResource contains only Message APIs and CommentResource contains Comment APIs. Below is the code i'm using, Your solution gave me new direction to look into the problem. Thanks :) – prranay Oct 30 '16 at 15:31
  • @RestController @RequestMapping("/messages/{messageId}/comments") public class CommentResource { ////// Will be much better. Since comments are always will be part of some message and you have to have some message Id to get the comments. So using common uri path as much as possible to the class level is better. – MrSham May 05 '18 at 02:26
  • You did not answer his question. He asked for a JAX-RS subresource alternative, you just showed him how to give his REST service a URL – Ahmed Anwar Sep 09 '18 at 16:46
6

What you are looking for is supported in JAX-RS implementations such as Jersey and is called Sub-Resources. When building large APIs which become nested in nature, sub resources are an extremly useful feature.

Spring Boot default rest implementation is not a JAX-RS but SpringMVC. Whilst it is possible to use Jersey in Spring Boot, it's a bit involved trying to set it up, and doesn't appear to be well used/supported in the community.

On a side note, DropWizard is awesome!

Paul Murphy
  • 106
  • 1
  • 4
2

I'm also being, forcedly, migrated from JAX-RS to Spring-MVC.

I'm still looking for an elegant way to do this just like I do with JAX-RS.

I'm sharing what I've tried.

@RestController
@RequestMapping("parents")
public class ParentsController {

    @RequestMapping(method = RequestMethod.GET,
                    produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<List<Parent>> read() {
    }

    @RequestMapping(method = RequestMethod.GET,
                    path = "/{id:\\d+}",
                    produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<Parent> read(@PathVariable("id") final long id) {
    }
}

And the ChildrenController.

@RestController
@RequestMapping("/parents/{parentId:\\d+}/children")
public class ChildrenController {

    @RequestMapping(method = RequestMethod.GET,
                    produces = {MediaType.APPLICATION_JSON_VALUE})
    @ResponseBody
    public List<Child> read(@PathVariable("parentId") final long parentId) {
    }

    @RequestMapping(method = RequestMethod.GET, path = "/{id:\\d+}",
                    produces = {MediaType.APPLICATION_JSON_VALUE})
    @ResponseBody
    public Child read(@PathVariable("parentId") final long parentId,
                     @PathVariable("id") final long id) {
    }
}

Two problems I've found,

@PathVariable is not applicable on fields.

I just can't do @PathVariable("parentId") private long parentId;

No free will for multiple mapping for ChildrenController

A beauty of JAX-RS is that we can map ChildrenController for different paths like even the ChildrenController has a class-level @Path with it.

@Path("/children");
public class ChildrenResource {
}

@Path("/parents")
public class ParentsResource {

    @Path("/{id}/children")
    public ChildrenResource resourceChildren() {
    }
}


/children
/parents/{id}/children
Jin Kwon
  • 20,295
  • 14
  • 115
  • 184
  • you can map several PathVariables to a single bean: ' @GetMapping(value = "/{id}/{domainValue}", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity retrieve( @ParameterObject MyRequestMultipleID id)' – eduyayo May 17 '23 at 06:30
2

You can keep it simple. Just create two classes and use a constant to refer the children resource with parent resource. This helps to make a link between the two classes and make the developer understand the relationship between them.

So:

@RequestMapping(value= MessageController.URL)
public class MessageController {
    public static final String URL= "/messages";
}

And:

@RequestMapping(value = MessageController.URL + "/{idMessage}/comments")
public class CommentController {

}

You can also split the controllers in different packages, showing this hierarchy in your package organization too:

com.company.web.message.MessageController
com.company.web.message.comment.CommentController 
Dherik
  • 17,757
  • 11
  • 115
  • 164
0

MessagesController.java

@RestController
@RequestMapping(value = "/messages")
public class MessageController {

    @Autowired
    private MessagesService messageService;

}

CommentController.java

@RestController
@RequestMapping("/messages/{messageId}/comments")
public class CommentController {


    @GetMapping
    public List<Comment> getComments(@PathVariable("messageId") Long messageId) {
        System.out.println("Get "+messageId);

        return null;
    }

}
PraKhar
  • 119
  • 1
  • 9
0

MessageResource.java

@RestController
@RequestMapping("/messages")
public class MessageResource {

    MessageService messageService = new MessageService();

    // as usual messages related CRUD operations
}

CommentResource.java

@RestController
@RequestMapping("messages/{messageId}/comments")
public class CommentResource {

    private CommentService commentService = new CommentService();

    @RequestMapping(method = RequestMethod.GET, value="/abc")
    public String test2() {
        return "this is test comment";
    }
}
Ritu Gupta
  • 2,249
  • 1
  • 18
  • 11