0

I'm trying to create a Spring MVC controller that enables creation and editing of a simple domain object (a Player) using an HTML form. And preferably in a RESTful manner. I've hit a snag with editing when there is an error in the submitted form. I want the client (browser) to display the error messages and the original form, so the user can correct it and resubmit.

But I can't get that to work. On an error I can get the web application to redisplay the original form, but without the error messages. I think because my code redirects to the form page in that case. I tried removing the redirection, but then the web server complains that PUT is not permitted for the resource. What do I need to do?

Here is the relevant code for my controller:

 @Controller
 @RequestMapping({
    "/player"
 })
 public class PlayerController {

    @RequestMapping(value = "/{id}/edit", method = RequestMethod.PUT)
    public String editPlayer(@PathVariable("id")
    final long id, @Valid
    @ModelAttribute(Model.PLAYER)
    final PlayerModel player, final BindingResult result) {
       if (!result.hasErrors()) {
          final Player playerEntity = playerService.find(id);
          playerEntity.setName(player.getName());
          playerService.update(playerEntity);
          return "redirect:/player/" + id;
       } else {
          return "redirect:/player/" + id + "/edit";
       }
    }

    @RequestMapping(value = "/{id}/edit", method = RequestMethod.GET)
    public String showEditPlayerPage(@PathVariable("id")
    final long id, final org.springframework.ui.Model model) {
       createModel(id, model);
       return View.EDIT_PLAYER;// the player editing page
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String showPlayerPage(@PathVariable("id")
    final long id, final org.springframework.ui.Model model) {
       // ...
       return View.PLAYER;// the read-only view of a player
    }

    // Other code
 }

I have JSR-303 validation annotations on the PersonModel, which triggers a validation failure if the name is too short or too long. The HTML for the edit form is this:

 <form:form commandName="player" method="PUT">
    <fieldset>
       <table>
          <tr>
             <th><label for="player_name">Player Name:</label></th>
             <td><form:input path="name" size="64" maxlength="64"
                   id="player_name" /> <br /> <small
                id="player_name_msg">Not empty, but no more
                   than 64 characters.</small> <form:errors path="name"
                   class="error" /></td>
          </tr>
       </table>
       <input type="submit" value="Submit"></input>
    </fieldset>
 </form:form>

Edit:

Just to be clear, everything works OK for the case that the form is valid. I have the servlet filter for translating PUT to POST, and it seems to be working OK.


Edit 2:

Experimentation and tinkering showed that my controller was being executed; the rejection of the PUT happens after execution of my controller. It seems that Spring does not like to have a view-name for the response to a PUT.

Community
  • 1
  • 1
Raedwald
  • 46,613
  • 43
  • 151
  • 237

2 Answers2

0

You need to add HiddenHttpMethodFilter in your web.xml in order to make PUT/DELETE work:

<filter>
    <filter-name>httpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>httpMethodFilter</filter-name>
    <servlet-name>your-servlet</servlet-name>
</filter-mapping>

From Spring Documentation:

... While HTTP defines these four methods, HTML only supports two: GET and POST. Fortunately, there are two possible workarounds: you can either use JavaScript to do your PUT or DELETE, or simply do a POST with the 'real' method as an additional parameter (modeled as a hidden input field in an HTML form). This latter trick is what Spring's HiddenHttpMethodFilter does. This filter is a plain Servlet Filter and therefore it can be used in combination with any web framework (not just Spring MVC). Simply add this filter to your web.xml, and a POST with a hidden _method parameter will be converted into the corresponding HTTP method request.

Complete reference here.

EDIT:

Try using <url-mapping/> tag instead of <servlet-name/>:

<filter>
    <filter-name>httpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>httpMethodFilter</filter-name>
    <url-mapping>/*</url-mapping>
</filter-mapping>
jelies
  • 9,110
  • 5
  • 50
  • 65
  • Everything works OK for the case that the form is valid. I have the servlet filter for translating PUT to POST, and it seems to be working OK. Its the case that the form has errors that is problematic. – Raedwald Aug 28 '12 at 09:40
  • what does it mean problematic? same error as before but only when form is invalid? :( Remember that `` with Spring's filter, translates POST requests to PUT requests, not PUTs to POSTs. – jelies Aug 28 '12 at 10:04
  • "what does it mean problematic": the editing form is displayed (what I want), but with no error messages (which I want to be present). – Raedwald Aug 28 '12 at 11:02
  • have you tried to use `method=post` and `@RequestMapping(value = "/{id}/edit", method = RequestMethod.POST)` to check if this shows the errors? – jelies Aug 28 '12 at 11:07
0

So, a causes of my problem is the insistence on using HTTP PUT for altering a player entity (resource). I insisted on doing that because I believed that was the pure RESTful way of doing things: POST for creation, PUT for alteration. But it seems I and others are mistaken: it's OK to use POST for alteration.

I altered my controller thus:

 @Controller
 @RequestMapping({
    "/player"
 })
 public class PlayerController {

    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void editPlayer(
       @PathVariable("id") final long id,
       @Valid @ModelAttribute(Model.PLAYER) final PlayerModel player,
       final BindingResult result) {
       // ...
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    public String editPlayerWithView(
       @PathVariable("id") final long id,
       @Valid @ModelAttribute(Model.PLAYER) final PlayerModel player,
       final BindingResult result) {
       // ...
       if (result.hasErrors()) {
          return View.EDIT_PLAYER;
       } else {
          // ...
          return View.PLAYER;
       }
    }

    @RequestMapping(value = "/{id}/edit", method = RequestMethod.GET)
    public String showEditPlayerPage(
       @PathVariable("id") final long id,
       final org.springframework.ui.Model model) {
       // ...
       return View.EDIT_PLAYER;
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String showPlayerPage(
       @PathVariable("id") final long id,
       final org.springframework.ui.Model model) {
       // ...
       return View.PLAYER;
    }

And I made a simple change to my form:

 <form:form commandName="player" action="/player/${player.id}">
    <!-- ... -->
 </form:form>

Now I can view, create and alter player entities (resources), with creation and alteration using HTML forms, and attempts at invalid creation or alteration resulting in errors messages being shown to the user in the form.

I could sub title my answer How I Learned to Stop Worrying and Love the POST.

Community
  • 1
  • 1
Raedwald
  • 46,613
  • 43
  • 151
  • 237
  • As this answer says: http://stackoverflow.com/a/2691891/545127 `PUT` is for idempotent alteration operations, `POST` need not be idempotent. It transpires that for my controller there is no distinction between idempotent and not-idempotent alterations (they are all idempotent), so the distinction between `PUT` and `POST` is not important. – Raedwald Aug 28 '12 at 21:55
  • The important thing for REST is not POST/PUT distinctions, but that the operations can be found by self discovering (following links and examining meta-data): http://stackoverflow.com/a/5105426/545127 – Raedwald Aug 28 '12 at 22:28