views:

56

answers:

1

I've went thru Spring documentation and source code and still haven't found answer to my question.

I have these classes in my domain model and want to use them as backing form objects in spring-mvc.


public abstract class Credentials {
  private Long     id;
  ....
}
public class UserPasswordCredentials extends Credentials {
  private String            username;
  private String            password;
  ....
}
public class UserAccount {
  private Long              id;
  private String            name;
  private Credentials       credentials;
  ....
}

My controller:


@Controller
public class UserAccountController
{
  @RequestMapping(value = "/saveAccount", method = RequestMethod.POST)
  public @ResponseBody Long saveAccount(@Valid UserAccount account)
  {
    //persist in DB
    return account.id;
  }

  @RequestMapping(value = "/listAccounts", method = RequestMethod.GET)
  public String listAccounts()
  {
    //get all accounts from DB
    return "views/list_accounts";
  }
  ....
}

On UI I have dynamic form for the different credential types. My POST request usually looks like:


name                    name
credentials_type        user_name
credentials.password    password
credentials.username    username

Following exception is thrown if I try to submit request to the server :


org.springframework.beans.NullValueInNestedPathException: Invalid property 'credentials' of bean class [*.*.domain.UserAccount]: Could not instantiate property type [*.*.domain.Credentials] to auto-grow nested property path: java.lang.InstantiationException
    org.springframework.beans.BeanWrapperImpl.newValue(BeanWrapperImpl.java:628)

My initial thought was to use @ModelAttribute


    @ModelAttribute
    public PublisherAccount prepareUserAccountBean(@RequestParam("credentials_type") String credentialsType){
      UserAccount userAccount = new PublisherAccount();
      Class credClass = //figure out correct credentials class;
      userAccount.setCredentials(BeanUtils.instantiate(credClass));
      return userAccount;
    }

Problem with this approach is that prepareUserAccountBean method get called before any other methods (like listAccounts) as well which is not appropriate.

One robust solution is to move out both prepareUserAccountBean and saveUserAccount to the separate Controller. It doesn't sound right : I want all user-related operations to reside in the same controller class.

Any simple solution? Can I utilize somehow DataBinder, PropertyEditor or WebArgumentResolver?

Thank you!!!!!

A: 

I'm not sure, but you should be using ViewModel classes on your controllers instead of Domain Objects. Then, inside your saveAccount method you would validate this ViewModel and if everything goes right, you map it into your Domain Model and persist it.

By doing so, you have another advantage. If you add any other property to your domain UserAccount class, e.g: private bool isAdmin. If your web user send you a POST parameter with isAdmin=true that would be bind to user Domain Class and persisted.

Well, this is the way I'd do:

public class NewUserAccount {
    private String name;
    private String username;
    private String password;
}  

@RequestMapping(value = "/saveAccount", method = RequestMethod.POST)
public @ResponseBody Long saveAccount(@Valid NewUserAccount account)
{
    //...
}
Guilherme Oenning
But it still doesn't solve original problem : how to bind an inherited class.
Denys K.