Data binding of an abstract class in spring-mvc - java

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!!!!!

I can't see any simple and elegant solution. Maybe because the problem is not how to data bind abstract classes in Spring MVC, but rather : why having abstract classes in form objects in the first place ? I think you shouldn't.
An object sent from the form to the controller is called a "form (backing) object" for a reason : the object attributes should reflect the form fields. If your form has username and password fields, then you should have username and password attributes in your class.
So credentials should have a UserPasswordCredentials type. This would skip your "abstract instantiation attempt" error. Two solutions for this :
Recommended : you change the type of UserAccount.credentials from Credentials to UserPasswordCredentials. I mean, what Credentials could a UserAccount possibly have, except a UserPasswordCredentials ? What's more, I bet your database userAccounts have a username and password stored as credentials, so you could as well have a UserPasswordCredentials type directly in UserAccount. Finally, Spring recommends using "existing business objects as command or form objects" (see doc), so modifying UserAccount would be the way to go.
Not recommended : you keep UserAccount as is, and you create a UserAccountForm class. This class would have the same attributes as UserAccount, except that UserAccountForm.credentials has a UserPasswordCredentials type. Then when listing/saving, a class (UserAccountService for example) does the conversion. This solution involves some code duplication, so only use it if you have a good reason (legacy entities you cannot change, etc.).

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)
{
//...
}

Related

How to map a request in Spring MVC to a Java Object

I'm new to Spring, and since Spring provides many ways to map an HTTP request to Java objects, I'm hoping someone could advice me how to resolve this:
I have a client that sends a request having
ContentType: application/x-www-form-urlencoded
Some of the request parmeters have names such as
"form_data[orderStatus]", "form_data[orderNumber]", etc'
I have no control over this client!
I have a java class (#Component) called MyOrder which looks as follows:
#component
#Scope("prototpe")
public class MyOrder {
private String orderStatus;
private String orderNumber;
//etc'
public void setOrderStatus(String orderStatus) {
this.orderStatus = orderStatus;
}
//public setter for all other properties, as the above
}
What is the simplest way to create an instance of MyOrder
populated with all values of all "form_data[]", so that I can have a controller method having a signature that includes a MyOrder parameter, such as:
public ModelAndView saveNewOrder( #RequestParam("foo") String foo,
#ModelAttribute("myOrder") MyOrder anOrder) {
//... impl'n here
}
The best solution I could think of was to use a Web Filter which would flaten request params names such as "form_data[attrib1]" to "attrib1", and then Spring would do all the work of populating the MyOrder instance.
One disadvantage of this is that the request may have both "form_data[attrib1]" and "attrib1" parameters. For example:
form_data[orderStatus]=ok
orderStatus=fail
In this case i want MyOrder.orderStatus to have the value "ok".
Any good way of utilizing Spring create MyOrder from the request?
As an alternative, that does not use the class MyOrder, is there a way to have Spring map all the form_data[] parameters and their values to a map, so that i can have the controller method below?
public ModelAndView saveNewOrder( #RequestParam("foo") String foo,
<some annotation> #Map<String,String> formFieldsOfAnOrder) {
//... impl'n here
orderStatus = formFieldsOfAnOrder.get("orderStatus");
//or at least:
orderStatus = formFieldsOfAnOrder.get("form_data[orderStatus]");
}

Spring: use different beans in one controller

I have following issue: we've been developing website using Spring tools 3.8.3 and one of the option is to allow users to reset their passwords. In order to do that they need to specify their email address and validate captcha. When these steps are completed data is sent to ResetPassword controller which does all necessary checks and work. Controller has following address:
website.com/path/resetPassword/
And has following structure where it extends abstract controller template to define the input and output beans:
#RequestMapping(path="website.com/path/resetPassword/",
consumes = "application/JSON",
produces = "application/JSON",
method = "POST")
public class ResetPassController extends ControllerTemplate<ResetPassCaptchaBean, ResponseBean>{
// Autowired field declaration
// Couple of methods calling services
}
The bean also has only two fields - email and captcha accordingly. Captcha field uses annotation to check if either it's valid or not:
public class ResetPassCaptchaBean extends ResetPassBean {
#CaptchaValid
private String captcha;
public void setCaptcha(String captcha) {
this.captcha = captcha;
}
public String getCaptcha() {
return captcha;
}
}
public class ResetPassBean {
private String email;
public void setEmail(String email) {
this.email= email;
}
public String getCaptcha() {
return email;
}
}
Now we are adding mobile API and it's necessary to do the same procedure but without using captcha validation. Therefore we're simply using the bean without captcha field. Since captcha is validated using the annotation in the bean it's completely not used in the controller itself and therefore we'd like to have the same controller both for handling requests from web site and from mobile API.
I understand that it's possible to create two different controllers and move all the implementation to service layer (read other class) but then there still would be two identical classes with only one line difference
public class ResetPassController extends ControllerTemplate<ResetPassCaptchaBean, ResponseBean>{
//Autowiring same stuff
//Calling same methods
}
and
public class MobileResetPassController extends ControllerTemplate<ResetPassBean, ResponseBean>{
//Autowiring stuff
//Calling same methods
}
So the question is: leave it with two identical controllers or is there other solution?
Thanks!

Spring IOC DI with runtime parameters

I'm relativity new to IOC and DI, so I'm guessing that I am missing some high-level design principle here, but I cannot figure out how to get my architecture working.
I have a REST API endpoint that accepts two pieces of POST data: customer ID, and Type ID. The rest api then needs to return a set of data for that specific customer/type combo.
Here is a crude picture of what I am doing:
The controller is taking the entity IDs passed in via post data, and via a JPA repository getting the appropriate Entities for them.
I then construct a data generator object (that takes the entities as constructor parameters), and use that to handle all of the data gathering for the API.
The Problem: because the Data Generator takes the two dynamic constructor parameters, it cannot be DI'ed into the Controller, but instead must be made with new. Inside of the Data Generator, however, I need access to JPA repositories. The only way to get access to these repositories is via DI. I cannot DI however, as the object was new'ed not DI'ed by the IOC container.
Is there a way to architect this so that I don't have this problem? Am I breaking some rule regarding IOC? Do I have wrong assumptions somewhere? Any advice is appreciated.
Thanks!
Edit: Pseudo code for Data Generator
public class DataGenerator {
private Customer customer;
private Type type
public DataGenerator(Customer customer, Type type) {
this.cusomter = customer;
this.type = type;
}
public generateData() {
if(customer == x && type == y) {
//JPA REPOSITORY QUERY
} else {
//DIFFERENT JPA REPOSITORY QUERY
}
}
}
I think you may have gotten confused somewhere along the line. You should have a Service that hits your repositories, and provide the information to the controller. One crude setup would be something like this.
#Controller
public MyController {
#AutoWired
private DataService dataService;
#RequestMapping(value = "/", method = RequestMethod.GET)
private DataGenerator readBookmark(#PathVariable Long customerId, #PathVariable Integer typeId) {
return dataService.getData(customerId, typeId);
}
}
#Service
public class DataService {
#AutoWired
private JPARepository repository;
public DataGenerator getData(long customerId, int typeId) {
Type typeDetails = repository.getType(typeId);
Customer customerDetails = repository.getCustomer(customerId);
return new DataGenerator(customerDetails, typeDetails);
}
}

Spring Controller: use domain objects as #RequestBody

I have a domain object class User (it is a JPA entity):
#Entity
public class User {
private String name;
private boolean enabled = true;
// getters/setters
}
And I am trying to offer a REST API to allow clients to create new users, using Spring 3 MVC:
#Controller
public class UserController {
#RequestMapping(value="/user", method=RequestMethod.POST)
#ResponseBody
public String createRealm(#RequestBody User user) {
user.setEnabled(true); // client is not allowed to modify this field
userService.createUser(user);
...
}
}
It works great, but I do not know if it is a good idea to use the domain objects as #RequestBody, because I have to protect some fields that should not be directly modified by the client (i.e. "enabled" in this case).
What are the pros/cons of these alternatives:
Use the domain objects and protect the fields the user is not allowed to modify (for example set them to null or to its default value by hand)
Use a new set of auxiliar objects (something similar to a DTO), such as a UserRequest that only contains the fields I want to expose through the REST API, and map them (i.e. with Dozer) to the domain objects.
The second alternative looks like this:
#Entity
public class User {
private String name;
private boolean enabled = true;
// getters/setters
}
public class UserRequest {
private String name;
// enabled is removed
// getters/setters
}
#Controller
public class UserController {
#RequestMapping(value="/user", method=RequestMethod.POST)
#ResponseBody
public String createRealm(#RequestBody UserRequest userRequest) {
User user = ... // map UserRequest -> User
userService.createUser(user);
...
}
}
Is there any other way that avoids code duplication and is easier to maintain?
There is another option - you can disallow the submission of a given set of properties, using the DataBinder.setDisallowedFields(..) (or using .setAllowedFields(..))
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields(..);
}
This is fine if you have one or two properties that differ.
Otherwise, having a special object (like ProfileDetails or UserRequest) makes more sense. I am using such a DTO-like object for this scenario and then transfer the fields with BeanUtils.copyProperties(..) from commons-beanutils
A third, perhaps better option, is to put all profile-related fields into a separate entity (mapped with #OneToOne with user) or to an #Embeddable object, and use it instead.

Spring MVC and JSR-303 hibernate conditional validation

I've a form I want to validate. It contains 2 Address variables. address1 has always to be validated, address2 has to be validated based on some conditions
public class MyForm {
String name;
#Valid Address address1;
Address address2;
}
public class Address {
#NotEmpty
private String street;
}
my controller automatically validates and binds my form obj
#RequestMapping(...)
public ModelAndView edit(
#ModelAttribute("form")
#Valid
MyForm form,
BindingResult bindingResult,
...)
if(someCondition) {
VALIDATE form.address2 USING JSR 303
the problem is that if I use the LocalValidatorFactoryBean validator i can't reuse the BinidingResult object provided by Spring. The bind won't work as the target object of 'result' is 'MyForm' and not 'Address'
validate(form.getAddress2(), bindingResult) //won't work
I'm wondering what's the standard/clean approach to do conditional validation.
I was thinking in programmatically create a new BindingResult in my controller.
final BindingResult bindingResultAddress2 = new BeanPropertyBindingResult(address2, "form");
validate(form.getAddress2(), bindingResultAddress2);
but then the List of errors I obtain from bindingResultAddress2 can't be added to the general 'bindingResult' as the field names are not correct ('street' instead of 'address2.street') and the binding won't work.
Some dirty approach would be to extend BeanPropertyBindingResult to accept some string to append to the fields name.. do you have a better approach?
The standard approach for validating hierarchical structures is to use pushNestedPath()/popNestedPath(), though I'm not sure how it plays with JSR-303:
bindingResult.pushNestedPath("address2");
validate(form.getAddress2(), bindingResult);
bindingResult.popNestedPath();
I've never tried myself, but I think the correct approach is using validator groups.
First of all, let's see #javax.validation.Valid API
Mark an association as cascaded. The associated object will be validated by cascade.
When Spring framework uses #Valid as a marker to validate its command objects, it corrupts its purpose. Spring should instead create your own specific annotation which specifies the groups which should be validated.
Unfortunately, you should use Spring native Validator API if you need to validate some groups
public void doSomething(Command command, Errors errors) {
new BeanValidationValidator(SomeUserCase.class, OtherUserCase.class)
.validate(command, errors);
if(errors.hasErrors()) {
} else {
}
}
BeanValidationValidator can be implemented as
public class BeanValidationValidator implements Validator {
javax.validation.Validator validator = ValidatorUtil.getValidator();
private Class [] groups;
public BeanValidationValidator(Class... groups) {
this.groups = groups;
}
public void validate(Object command, Errors errors) {
Set<ConstraintViolation<Object>> constraintViolationSet = validator.validate(command, groups);
for(ConstraintViolation<Object> constraintViolation: constraintViolationSet) {
errors.rejectValue(constraintViolation.getPropertyPath().toString(), null, constraintViolation.getMessage());
}
}
}

Categories