In my Spring 4 driven portlet I have a data-object which contains a date-field. In the JSP-view I have two separate fields for it, date (dd/mm/yyyy) and time (hh/mm). The action handler in my controller receives the form-data by using the #ModelAttribute-annotation:
#ActionMapping(params = "action=update")
public void onUpdate(#Valid #ModelAttribute("myObject") MyObject myObject,
BindingResult bindingResult, ActionResponse response, SessionStatus status)
{
if (!bindingResult.hasErrors())
myObjectService.save(myObject);
else
// provide 'myObject' in the view which displays validation errors
}
I need to merge date and time before the form-data is validated an onUpdate() receives myObject, since in MyObject there is only one java.util.Calendar-field for the whole date available.
Idea 1 to work around this need
Now I thought, I could also split the date into two separate fields in MyObject and provide a getter which merges the values on demand:
#Column(name = "begin")
public Calendar getBegin()
{
// return calendar object built from this.beginDate and this.beginTime
}
but I think this is not a good idea for several reasons (see this question: Hibernate Annotations - Which is better, field or property access?)
I want the model-object to be a mirror of the database-record and hence it should be validated before getting assigned.
Idea 2
Another approch would be, to create or modify the calendar-object in myObject on demand, when setting the date or time:
#Column(name = "begin")
#Temporal(TemporalType.TIMESTAMP)
private Calendar begin;
public void setBeginDate(String value)
{
// assign a new calendar object to "begin", if "begin" is null
// set only day, month and year components on this calendar object
}
public void setBeginTime(String value)
{
// see "setBeginDate", do the same with hours and minutes
}
The problem here is, that a new calendar-object is created if only one of the fields "date" or "time" is valid. The field in the view is filled with the current date or current time (depending on which value was correct)
I can even solve this problem, by adding another private isValidDate-flag to the model. But I think this is an unclean solution.
Conclusion
I think there is a big difference between myObject for the controller and myObject as an actual model-object. myObject should be a model-object, as soon as being validated and "mapped".
So here my questions:
Do you think that the last point reveals using #ModelAttribute as a bad idea generally?
Or is there a way mapping and validating form-data BEFORE the MyObject-instance is created?
If not, how would you recommend to solve the problem?
EDIT : #initBinder is OK the other way around (1 field -> N attributes)
I don't know if the framework can do what you want (N fields -> 1 attribute) out of the box without getting into the binding plumbing.
Here are two simple solutions to get around the issue :
Do it the old "Struts" way and create a MyObjectForm object that you will use exclusively in your view, and then transform it in your controller to MyObject.
You can still use your #Valid at the service layer MyObjectService which is technically sound.
If MyObject does not contain dozens of attributes, Forget about #ModelAttribute + Controller layer validation and just use #RequestParam for each field. Service layer validation is still in play.
You can certainly hack something that will allow you to "bind" your form data to your object manually using Spring MVC's internal facilities, but in the end it will be something like my 2nd point, with much more confusing plumbing.
Related
Recently I was working on a little RESTful API using Spring and I came across the ModelAttribute annotation.
I noticed that there is some very interesting behavior associated with it, mainly the fact that you can stick it onto a method and it will get called before the handler for a given request is called, allowing you to do anything before data is bound to the arguments of your handler method.
One usage that comes to mind is default values:
#ModelAttribute("defaultEntity")
public Entity defaultEntity() {
final var entity = new Entity();
entity.setName("default name");
return entity;
}
#PostMapping("/entity")
public Entity createNewEntity(#Valid #ModelAttribute("defaultEntity") Entity entity) {
dao.saveEntity(entity);
return entity;
}
In this case, when a POST request comes to /entity, the first thing that will happen is that defaultEntity will get called, creating an entity with some default values pre-filled. Then, Spring will bind the incoming data into it (potentially overwriting the defaults or keeping them as-is) and then pass it into the createNewEntity handler. This is actually pretty nice, IMO.
Another surprising fact is that the annotated method can actually take parameters in much the same way as the handler method. A simple way to do partial entity updates could be something like this:
// first fetch the original entity from the database
#ModelAttribute("originalEntity")
public Entity originalEntity(#PathVariable("id") long id ) {
return dao.getEntity(id);
}
// then let Spring bind data to the entity and validate it
#PostMapping("/entity/{id}")
public Entity updateEntity(#Valid #ModelAttribute("originalEntity") Entity entity) {
// and finally we save it
dao.saveEntity(entity);
return entity;
}
Again, this is surprisingly easy.
Even more surprising is that different model attributes can depend on each other, so you can have a complicated multi-stage monster if you want:
// first fetch the original entity from the database
#ModelAttribute("originalEntity")
public Entity originalEntity(#PathVariable("id") long id ) {
return dao.getEntity(id);
}
// then let Spring bind data to the entity, validate it and do some processing to it
#ModelAttribute("boundAndValidatedEntity")
public Entity boundAndValidatedEntity(#Valid #ModelAttribute("originalEntity") Entity entity) {
processEntity(entity);
return entity;
}
// finally check that the entity is still valid and then save it
#PostMapping("/entity/{id}")
public Entity updateEntity(#Valid #ModelAttribute(value = "boundAndValidatedEntity", binding = false) Entity entity) {
dao.saveEntity(entity);
return entity;
}
Obviously not all of the model attributes have to be of the same type, some can depend on multiple arguments from different places. It's like a mini-DI container within a single controller.
However, there are some drawbacks:
as far as I can tell, it only works with query parameters and there is no way to make it work with other kinds of request parameters, such as the request body or path variables
all of the ModelAttribute-annotated methods within a single controller will always be called, which can
have a performance impact
be annoying to work with, since Spring will need to be able to gather all of the method's arguments (which may be impossible, for example when they reference a path variable that doesn't exist in the current request)
So, while ModelAttribute doesn't really seem too useful by itself because of these issues, I feel like the main idea behind it - essentially allowing you to control the construction of a method's parameter before it's bound/validated while being able to easily access other request parameters - is solid and could be very useful.
So, my question is simple - is there anything in Spring that would essentially act like ModelAttribute but without the drawbacks that I mentioned? Or maybe in some 3rd party library? Or maybe I could write something like this myself?
I've a simple RESTful API based on Spring MVC using a JPA connected MySQL database. Until now this API supports complete updates of an entity only. This means all fields must be provided inside of the request body.
#ResponseBody
#PutMapping(value = "{id}")
public ResponseEntity<?> update(#Valid #RequestBody Article newArticle, #PathVariable("id") long id) {
return service.updateById(id, newArticle);
}
The real problem here is the validation, how could I validate only provided fields while still require all fields during creation?
#Entity
public class Article {
#NotEmpty #Size(max = 100) String title;
#NotEmpty #Size(max = 500) String content;
// Getters and Setters
}
Example for a partial update request body {"content": "Just a test"} instead of {"title": "Title", "content": "Just a test"}.
The actual partial update is done by checking if the given field is not null:
if(newArticle.getTitle() != null) article.setTitle(newArticle.getTitle());
But the validation of course wont work! I've to deactivate the validation for the update method to run the RESTful service. I've essentially two questions:
How can I validate only a "existing" subset of properties in the
update method while still require all fields during creation?
Is there a more elegant way for update partially then checking for
null?
The complexity of partial updates and Spring JPA is that you may send half of the fields populated, and even that you will need to pull the entire entity from the data base, then just "merge" both entity and the pojo, because otherwise you will risk your data by sending null values to the database.
But merging itself is kind of tricky, because you need to operate over each field and take the decision of either send the new value to the data base or just keep the current one. And as you add fields, the validation needs to be updated, and tests get more complex. In one single statement: it doesn't scale. The idea is to always write code which is open for extension and closed for modifications. If you add more fields, then the validation block ideally doesn't need to change.
The way you deal with this in a REST model, is by operating over the entire entity each time you need. Let's say you have users, then you first pull a user:
GET /user/100
Then you have in your web page the entire fields of user id=100. Then you change its last name. You propagate the change calling the same resource URL with PUT verb:
PUT /user/100
And you send all the fields, or rather the "same entity" back with a new lastname. And you forget about validation, the validation will just work as a black box. If you add more fields, you add more #NotNull or whatever validation you need. Of course there may be situations where you need to actually write blocks of code for validation. Even in this case the validation doesn't get affected, as you will have a main for-loop for your validation, and each field will have its own validator. If you add fields, you add validators, but the main validation block remains untouchable.
I'm designing a REST service and am running into the issue that for a given object, I have multiple "states".
The object as it arrives on the initial POST operation.
The Object I store in our DB
The Object I return on a GET
The Object I expect on a PATCH
e.g.
class MyObject {
// Unwanted on POST
// Required on PATCH
// Included on GET
#JsonProperty("id")
private UUID id;
// Everywhere
#NonNull
#JsonProperty("name")
private String name;
// Field I need for internal processing but don't want included in REST.
private AuditTrail stuff;
#JsonCreator
#Builder
public MyObject(...) { ... }
}
...
#Get
public ResponseEntity myFunction(HttpServletRequest request,
#RequestBody #Valid MyObject requestBody) {
...
}
The issue I am running into is that on POST, when the id is omitted, the deserialization fails. I got around it using #JsonIgnoreProperties(), but now on PATCH, where I do want the id present, things work if it is omitted.
Another alternative we toyed with was to have two objects. The first one with the common fields for POST and the other extending from it with the rest, but it feel messy, especially as we deal with objects more complex than the simple example.
It's not actually a problem since I validate and sanitize inputs anyway, but I was wondering if there is a clean way in Jackson to solve this issue.
If you are planning a rest service then you don't need the id in the body anyway. The id will come from the url as a pathvariable:
POST myobjects
GET myobjects/{id}
PATCH myobjects/{id}
So I need to add a new field to a SOAP service response. The thing is that the field has to take the value from a persistent field. I cannot add that persistent field directly. The persistent field returns a "Calendar" instance, which is, in fact, a DATETIME from MySQL. The current object uses the XmlAdapter.
I did something like this:
class SomeClassImpl extends SomeClass
{
#Transient
#XmlSchemaType(name="someDate")
private String someDate;
...
public void defSomeDate()
{
this.someDate = this.getPersistentDate().toString();
}
public String retSomeDate()
{
return this.someDate();
}
}
The new field appears in the soap result, but the value is an exception, which I don't remember right now and I am not able to reproduce it now.
How would you do this? Is it possible to annotate a method instead of the member so it appears in the SOAP result? If yes, how would an annootation would look like?
Thank you in advance!
The problem was the following piece of code:
#XmlSchemaType(name="someDate")
There "name" parameter should be one of the standard data types for xml. In this case, because it contains the date and the time, it should be 'dateTime'. It could also be a string, but declaring it as dateTime makes the field more restrictive. Therefore, the correct annotation is:
#XmlSchemaType(name="dateTime")
With the date and time in mind, the second observation is that private String someDate; should be private CalendarsomeDate;, to be consistent and also for the actual code to work.
Annotating the methods is not required. Simply annotating the member/property is enough and as long as the member/property is set somewhere at runtime.
I hope this would be helpful for someone else too. It took me few hours to get this, but now I know how to proceed in the future.
I have tried and tried but can't figure out what is going on here.
I have a simple controller annotated using #Controller
I also have annotation for #SessionAttributes
I handle a GET request and put an object into the model.
When I get back the POST from the form, I only get back what the user has populated. I'm not getting back the complete object.
I'm new to SessionAttributes but I thought this preserved the whole object and when the object was read back in the method using #ModelAttribute, it would be merged the object (i.e. the object that the form changed). However, I'm not seeing this behavior.
Any help would be much appreciated.
Here is the relevant pieces from the code:
#Controller
#RequestMapping("/user")
#SessionAttributes("user")
public class UserController
{
// ...
#RequestMapping(value = "/{login}", method = RequestMethod.GET)
public String profile(Model model, #PathVariable("login") String login)
{
// ...
model.addAttribute("user", user);
// ...
}
#RequestMapping(value="/{login}", method = RequestMethod.POST)
public String saveProfile(#ModelAttribute("user") #Valid User user, BindingResult result, SessionStatus status)
{
if (result.hasErrors())
{
return "user/index";
}
// ...
status.setComplete();
return "redirect:/user/"+user.getLogin();
}
Do you see anything that I may have missed? I have spent almost a day trying to figure this out and just can't. Any help would be much appreciated.
Update: I figured out what the issue was. Answer posted below.
I figured out what was going after much laboring. I hope this saves somebody else the time.
The underlying problem here was twofold:
The object being saved in the session was decorated with some aspectj notations. Because of this the attribute values for the object were only returned by the appropriate get accessors.
I had hibernate validation in place (note the #Valid annotation in the method that handles the POST). The validator annotations were directly on each field (as below):
#NotNull
private String name;
Here is how I fixed it.
For testing purposes only, I removed the #Valid and noticed that even though the fields themselves seem to be NULL, we were saving the correct data in our backend store. This is what clued me into the root cause of this issue.
I figured the validator annotations were causinI moved the validator notation to get methods. So the code changed as follows:
private String name;
#NotNull
public String getName() {...}
I put the #Valid annotation back in and verified that the validation was no longer failing.
Hope it helps someone out there and save them a day of work. :)
I would not expect that spring merges the properties form session and form. You should separate the user that is submitted by the form, and the user from the session.
I had the same question as Azeem, and since he did not explicitly confirm that sessionattribute can be used to "merge" the original form backing object with the changes posted in the submit, I wanted to point out that yes, the changes from the form submit do get merged into the original form backing object.
There can be some issues with this approach as pointed out in
Spring MVC 3.0: How do I bind to a persistent object
but this approach is very helpful when you have a complex form backing object but you are only allowing the user to update a few of the object graph members in the form and not using hidden fields to maintain the remainder of the complex object in form elements.
When this approach is used without the use of the #SessionAttributes("xxx") annotation on the class, the returned form backing object is basically null except for those members specifically submitted by the form. This can make persisting the updated objects very difficult because you would have to combine the new updates into the original object yourself. But with use of the sessionattribute, the full updated form backing object provided after the submital makes persisting the object graph much easier.