views:

1321

answers:

3

I am trying to create a RESTful controller using Spring 3.0. The controller is for a management API for a portal application. The operations I want to perform are:

  • GET /api/portals to list all the portals
  • POST /api/portals to create a new portal
  • GET /api/portals/{id} to retrieve an existing portal
  • PUT /api/portals/{id} to update an existing portal
  • DELETE /api/portal/{id} to delete an existing portal

After annotating the controller as illustrated below I find the the operations to list all the portals or create a new portal do not get mapped.

So my questions are:

  • Have I annotated the class correctly?
  • Am I following the correct conventions for implementing a RESTful web service?
  • Might there be something broken in Spring?

The code extract below shows how I have annotated my class:

@Controller
@RequestMapping("/api/portals")
public final class PortalAPIController
{
    private final static Logger LOGGER = LoggerFactory.getLogger(PortalAPIController.class);

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String listPortals(final Model model)
    {
         PortalAPIController.LOGGER.debug("Portal API: listPortals()");
         .
         .
         return "portals";
    }

    @RequestMapping(value = "/", method = RequestMethod.POST)
    public String createPortal(@RequestBody final MultiValueMap<String, String> portalData, final Model model)
    {
        PortalAPIController.LOGGER.debug("Portal API: createPortal()");
        .
        .
        return "portal";
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String getPortal(@PathVariable("id") final String portalId, final Model model, final HttpServletResponse response)
        throws IOException
    {
        PortalAPIController.LOGGER.debug("Portal API: getPortal()");
        .
        .
        return "portal";
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    public String updatePortal(@PathVariable("id") final String portalId,
        @RequestBody final MultiValueMap<String, String> portalData, final Model model, final HttpServletResponse response)
        throws IOException
    {
        PortalAPIController.LOGGER.debug("Portal API: updatePortal()");
        .
        .
        return "portal";
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public String deletePortal(@PathVariable("id") final String portalId, final Model model, final HttpServletResponse response)
        throws IOException
    {
        PortalAPIController.LOGGER.debug("Portal API: deletePortal()");
        .
        .
        return "portal";
    }

    .
    .
}

During start-up I am seeing that Spring things it has registered the end-points:

2010-02-19 01:18:41,733 INFO [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping] - Mapped URL path [/api/portals/] onto handler [com.btmatthews.mars.portal.web.controller.PortalAPIController@141717f]
2010-02-19 01:18:41,734 INFO [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping] - Mapped URL path [/api/portals/{id}] onto handler [com.btmatthews.mars.portal.web.controller.PortalAPIController@141717f]
2010-02-19 01:18:41,734 INFO [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping] - Mapped URL path [/api/portals/{id}.*] onto handler [com.btmatthews.mars.portal.web.controller.PortalAPIController@141717f]
2010-02-19 01:18:41,735 INFO [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping] - Mapped URL path [/api/portals/{id}/] onto handler [com.btmatthews.mars.portal.web.controller.PortalAPIController@141717f]
2010-02-19 01:18:41,735 INFO [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping] - Mapped URL path [/api/portals] onto handler [com.btmatthews.mars.portal.web.controller.PortalAPIController@141717f]
2010-02-19 01:18:41,735 INFO [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping] - Mapped URL path [/api/portals.*] onto handler [com.btmatthews.mars.portal.web.controller.PortalAPIController@141717f]

But when I try to invoke my API using cURL

curl http://localhost:8080/com.btmatthews.minerva.portal/api/portals/

or

curl http://localhost:8080/com.btmatthews.minerva.portal/api/portals

I get the following errors:

2010-02-19 01:19:20,199 WARN [org.springframework.web.servlet.PageNotFound] - No mapping found for HTTP request with URI [/com.btmatthews.minerva.portal/api/portals] in DispatcherServlet with name 'portal'
2010-02-19 01:19:32,360 WARN [org.springframework.web.servlet.PageNotFound] - No mapping found for HTTP request with URI [/com.btmatthews.minerva.portal/api/portals/] in DispatcherServlet with name 'portal'

I get the same problem when I try to do a create:

curl -F ...... --request POST http://localhost:8080/com.btmatthtews.minerva/api/portals

But if try to operate on an existing resource (retrieve, update or delete) it works okay.

Update: The solution was provided in a comment by @axtavt. I was using <url-pattern>/api/*</url-pattern> in my web.xml servlet mapping. It needed to be changed to <url-pattern>/</url-pattern>

A: 

The URL you post in your curl excerpt

http://localhost:8080/portal/api/portals

does not match the URL in the Spring warning

/com.btmatthews.minerva.portal/api/portals

Without knowing how your webapp is set up, what context path it's at, what the Spring context looks like, etc., it's pretty hard to diagnose but this sounds like a big clue to me.

matt b
The correct context root was /com.btmatthews.minerva.portal rather portal. I updated the original question.
bmatthews68
A: 

Greetings...

By looking at your code. it seems like you are missing the "@" in your GET action on listPortals(), which could be reason it doesn't find any match when you do a curl on /api/portals/

--> RequestMapping(value = "/", method = RequestMethod.GET)
public String listPortals(final Model model)
{
    // ...
}

By the way, perhaps you already know this, since you have /api/portals mapping defined at class level, you can actually omit value="/" in the method mapping, something like this is sufficient:-

@RequestMapping(method = RequestMethod.GET)
public String listPortals(final Model model)
{
    // ...
}
limc
@limc - That was a transcription error on my part. Thanks for pointing it out and I've corrected the question.
bmatthews68
+1  A: 

Double check the url-pattern in your web.xml and compare it to your curl argument.

Here is an example I wrote which walks you through the whole Spring MVC process.

James Earl Douglas