Spring - How to use #RequestParam parameter of a Controller in a Custom Validator class? - spring

I've got a problem about validation in Spring MVC with Hibernate.
I want a validator that valid user input, but the validation must be done out of the controller, so, in a separate validation class.
The situation: this is the head of my controller in which I want to do the validation. I need that id to retrieve a list of Booking of a specific car.
#PostMapping(value = "/rent")
public ModelAndView vehicleRent(#ModelAttribute("newBooking") Booking booking, BindingResult bindingResult, #RequestParam("id") long id) {
But if i want to separate the logic out of this controller creating a custom validator, i have this result:
public class BookingValidator implements Validator {
#Autowired
VehicleBO vehicleBo;
#Override
public boolean supports(Class<?> type) {
return Booking.class.isAssignableFrom(type);
}
#Override
public void validate(Object o, Errors errors) {
Booking booking = (Booking) o;
//other code
rejectIfBookingExists(booking, 0, errors, "validation.booking.startdate.exists");
}
}
public boolean rejectIfBookingExists(Booking booking, long id, Errors errors, String key){
boolean exists = false;
List<Booking> vehicleBookings = vehicleBo.getVehicleBookings(id);
if (booking != null || booking.getStartDate() != null || booking.getFinishDate() != null) {
for (Booking b : vehicleBookings) {
if (booking.getStartDate().before((b.getFinishDate())) || booking.getStartDate().equals(b.getFinishDate())) {
errors.rejectValue("startDate", key);
exists = true;
break;
}
}
}
return exists;
}
}
In this way I cannot retrieve the list because i don't have the required id, could you explain me how to do that? Or,there are other ways to solve this problem?
Thanks!
EDIT:
This is the Booking class, as you can see it has a Vehicle object mapped inside
#Entity
public class Booking implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinTable(name="user_booking", joinColumns={#JoinColumn(name ="booking_id", referencedColumnName ="id")},
inverseJoinColumns={#JoinColumn(name ="user_id", referencedColumnName ="id")})
private User user;
#ManyToOne
#JoinColumn(name = "vehicle_id")
private Vehicle vehicle;
#DateTimeFormat(pattern = "dd/MM/yyyy")
private Date startDate;
#DateTimeFormat(pattern = "dd/MM/yyyy")
private Date finishDate;
public Booking() {
//getter and setter and other code
}
Any ideas?

Why don't you simply map the vehicle id as booking.vehicle.id in your form? Provided Vehicle has a no-arg constructor (which it probably does, being an entity), the Booking should come back in the POST request handler with an instantiated Vehicle, along with its id property set. You should then be able to access booking.vehicle.id from wihtin the validator.
You can use an input[type=hidden] for the booking.vehicle.id field. In your GET request for the view with the form, simply inject the vehicle id as a #PathVariable and copy it to your model, so that you could reference the value inside the form.

Related

Spring Data Audit, CreatedBy lost on Update [duplicate]

I am using the auditing capabilities of Spring Data and have a class similar to this:
#Entity
#Audited
#EntityListeners(AuditingEntityListener.class)
#Table(name="Student")
public class Student {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
private Long id;
#CreatedBy
private String createdBy;
#CreatedDate
private Date createdDate;
#LastModifiedBy
private String lastModifiedBy;
#LastModifiedDate
private Date lastModifiedDate;
...
Now, I believe I have configured auditing fine because I can see that createdBy, createdDate, lastModifiedBy and lastModifiedDate all are getting the correct values when I update the domain objects.
However, my problem is that when I update an object I am losing the values of createdBy and createdDate. So, when I first create the object I have all four values, but when I update it createdBy and createdDate are nullified ! I am also using the Hibernate envers to keep a history of the domain objects.
Do you know why do I get this behavior ? Why do createdBy and createdDate are empty when I update the domain object ?
Update: To answer #m-deinum 's questions: Yes spring data JPA is configured correctly - everything else works fine - I really wouldn't like to post the configuration because as you udnerstand it will need a lot of space.
My AuditorAwareImpl is this
#Component
public class AuditorAwareImpl implements AuditorAware {
Logger logger = Logger.getLogger(AuditorAwareImpl.class);
#Autowired
ProfileService profileService;
#Override
public String getCurrentAuditor() {
return profileService.getMyUsername();
}
}
Finally, here's my update controller implementation:
#Autowired
private StudentFormValidator validator;
#Autowired
private StudentRepository studentRep;
#RequestMapping(value="/edit/{id}", method=RequestMethod.POST)
public String updateFromForm(
#PathVariable("id")Long id,
#Valid Student student, BindingResult result,
final RedirectAttributes redirectAttributes) {
Student s = studentRep.secureFind(id);
if(student == null || s == null) {
throw new ResourceNotFoundException();
}
validator.validate(student, result);
if (result.hasErrors()) {
return "students/form";
}
student.setId(id);
student.setSchool(profileService.getMySchool());
redirectAttributes.addFlashAttribute("message", "Επιτυχής προσθήκη!");
studentRep.save(student);
return "redirect:/students/list";
}
Update 2: Please take a look at a newer version
#RequestMapping(value="/edit/{id}", method=RequestMethod.GET)
public ModelAndView editForm(#PathVariable("id")Long id) {
ModelAndView mav = new ModelAndView("students/form");
Student student = studentRep.secureFind(id);
if(student == null) {
throw new ResourceNotFoundException();
}
mav.getModelMap().addAttribute(student);
mav.getModelMap().addAttribute("genders", GenderEnum.values());
mav.getModelMap().addAttribute("studentTypes", StudEnum.values());
return mav;
}
#RequestMapping(value="/edit/{id}", method=RequestMethod.POST)
public String updateFromForm(
#PathVariable("id")Long id,
#Valid #ModelAttribute Student student, BindingResult result,
final RedirectAttributes redirectAttributes, SessionStatus status) {
Student s = studentRep.secureFind(id);
if(student == null || s == null) {
throw new ResourceNotFoundException();
}
if (result.hasErrors()) {
return "students/form";
}
//student.setId(id);
student.setSchool(profileService.getMySchool());
studentRep.save(student);
redirectAttributes.addFlashAttribute("message", "Επιτυχής προσθήκη!");
status.setComplete();
return "redirect:/students/list";
}
This still leaves empty the createdBy and createdDate fields when I do an update :(
Also it does not get the School value (which is not contained in my form because it is related to the user currently editing) so I need to get it again from the SecurityContext... Have I done anything wrong ?
Update 3: For reference and to not miss it in the comments: The main problem was that I needed to include the #SessionAttributes annotation to my controller.
Use updatable attribute of #Column annotation like below.
#Column(name = "created_date", updatable = false)
private Date createdDate;
This will retain the created date on update operation.
Your method in your (#)Controller class is not that efficient. You don't want to (manually) retrieve the object and copy all the fields, relationships etc. over to it. Next to that with complex objects you will sooner or alter run into big trouble.
What you want is on your first method (the GET for showing the form) retrieve the user and store it in the session using #SessionAttributes. Next you want an #InitBinder annotated method to set your validator on the WebDataBinder so that spring will do the validation. This will leave your updateFromForm method nice and clean.
#Controller
#RequestMapping("/edit/{id}")
#SessionAttributes("student")
public EditStudentController
#Autowired
private StudentFormValidator validator;
#Autowired
private StudentRepository studentRep;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
#RequestMapping(method=RequestMethod.GET)
public String showUpdateForm(Model model) {
model.addObject("student", studentRep.secureFind(id));
return "students/form";
}
#RequestMapping(method=RequestMethod.POST)
public String public String updateFromForm(#Valid #ModelAttribute Student student, BindingResult result, RedirectAttributes redirectAttributes, SessionStatus status) {
// Optionally you could check the ids if they are the same.
if (result.hasErrors()) {
return "students/form";
}
redirectAttributes.addFlashAttribute("message", "?p?t???? p??s????!");
studentRep.save(student);
status.setComplete(); // Will remove the student from the session
return "redirect:/students/list";
}
}
You will need to add the SessionStatus attribute to the method and mark the processing complete, so that Spring can cleanup your model from the session.
This way you don't have to copy around objects, etc. and Spring will do all the heave lifting and all your fields/relations will be properly set.
In my case #CreatedDate and #CreatedBy fields were not removed from databse during update, but were not queried by #Repository findOne method.
Changing
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
into
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
on #Entity class helped with that.

Find the updated fields on update request

I have a user entity as
#Entity
public class User {
#Id
private Long id;
private String name;
private String address;
// getter / setter
}
And controller method like:
#PutMapping(value = "/user")
public ResponseEntity<?> updateUser(#RequestBody User user) {
userRepository.save(user);
// ...
}
Now What I am trying to do is finding the field that is being updated.
Example:
If the only name is present I need some message like "Updated field is name".
Is there any better way other than comparing the fields one by one with the database stored values.
you need getUser method. for example: userRepository.getUser(user.id)
then you return the result

how not to consider #NotBlank in some methods

I'm doing a restful app in Spring boot,jpa,mysql. I have annoted some of my model fields #NotBlank to print an error in the creation of an object if those fields are blank.
Now when i'm updating, I don't want to get that error if I don't set some fields in my json body.My goal is to update just the fields which are present.
So I want to know if there is a way not to consider an #NotBlank in my updating method.
This is the code source :
For the Entity
public class Note implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank(name)
private String title;
#NotBlank
private String content;
//Getters and Setters
}
The controller
#RestController
#RequestMapping("/api")
public class NoteController {
#Autowired
NoteRepository noteRepository;
// Create a new Note
#PostMapping("/notes")
public Note createNote(#Valid #RequestBody Note note) {
return noteRepository.save(note);
}
// Update a Note
#PutMapping("/notes/{id}")
public Note partialUpdateNote(#PathVariable(value = "id") Long noteId,
#RequestBody Note noteDetails) {
Note note = noteRepository.findById(noteId)
.orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId));
//copyNonNullProperties(noteDetails, note);
if(note.getTitle()!= null) {
note.setTitle(noteDetails.getTitle());
}else {
note.setTitle(note.getTitle());
}
if(note.getContent()!= null) {
note.setContent(noteDetails.getContent());
}else {
note.setContent(note.getContent());
}
Note updatedNote = noteRepository.save(note);
return updatedNote;
}
// Delete a Note
#DeleteMapping("/notes/{id}")
public ResponseEntity<?> deleteNote(#PathVariable(value = "id") Long noteId) {
Note note = noteRepository.findById(noteId)
.orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId));
noteRepository.delete(note);
return ResponseEntity.ok().build();
}
}
ResourceNotFoundException is the class responsible to throws errors.
You can use groups for that.
Add two interfaces CreateGroup and UpdateGroup.
Use them by this way:
#NotBlank(groups = CreateGroup.class)
#Null(groups = UpdateGroup.class)
private String title;
In the create endpoint
#Valid #ConvertGroup(from = Default.class, to = CreateGroup.class) Note note
In the update endpoint
#Valid #ConvertGroup(from = Default.class, to = UpdateGroup.class) Note note
Probably you don't need UpdateGroup. It is just to show a common approach.
Also for the nested objects inside Note something like
#ConvertGroup(from = CreateGroup.class, to = UpdateGroup.class)
can be used.

JPA - Spring boot -#OneToMany persistence works fine but i get a strange object when returning Json object

I have two entities ( Category | product ) with #OneToMany bidirectional relationship.
#Entity
public class Category {
public Category() {
// TODO Auto-generated constructor stub
}
public Category(String name,String description) {
this.name=name;
this.description=description;
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long cid;
private String name;
private String description;
#OneToMany(mappedBy="category",cascade=CascadeType.ALL, orphanRemoval=true)
private Set<Product> products;
/..getters and setter.../
}
#Entity
public class Product {
public Product() {
// TODO Auto-generated constructor stub
}
public Product(long price, String description, String name) {
// TODO Auto-generated constructor stub
this.name=name;
this.description=description;
this.price=price;
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long pid;
private long price;
private String name;
private String description;
#ManyToOne
private Category category;
/..getters and setters../
}
in my controller I have a function /categoris that add a new category with one product, it works great and in my database I've got a foreign category id
But when i try to retrieve all the categories with responseBody i got a strange object exactely in category ( i want have in product, category : the category id instead of the object it's self )
public #ResponseBody Category create() {
Category c=new Category("LIGA","messi feghouli cristiano");
Product p=new Product(200,"jahd besaf","Real Madrid");
if(c.getProducts()!=null){
c.addProducts(p);
}else{
Set<Product> products=new HashSet<Product>();
products.add(p);
c.setProducts(products);
}
p.setCategory(c);
cDao.save(c); pDao.save(p);
return c;
}
#RequestMapping(value="/categories",method = RequestMethod.GET)
public #ResponseBody List<Category> categories() {
return cDao.findAll();
}
this is the strage object that i got :
{"cid":1,"name":"LIGA","description":"messi feghouli cristiano","products":[{"price":200,"name":"Real Madrid","description":"jahd besaf","category":{"cid":1,"name":"LIGA","description":"messi feghouli cristiano","products":[{"price":200,"name":"Real Madrid","description":"jahd besaf","category":{"cid":1,"name":"LIGA","description":"messi feghouli cristiano","products":[{"price":200,"name":"Real Madrid","description":"jahd besaf","category":{"cid":1,"name":"LIGA","description":"messi feghouli cristiano","products":
That's exactly as it should be.
If you wish to avoid a circular reference, use the #JsonBackReference annotation. This prevents Jackson (assuming you're using Jackson) from going into an infinite loop and blowing your stack.
If you want the ID instead of the entity details, then create getProductID & getCategoryID methods and annotate the entity accessor with #JsonIgnore.

Spring MVC with Hibernate Validator Mandatory for the database field, but not in the application

Problem with BindingResult hasErrors() in validation.
I have this code:
#RequestMapping(value = "/entity", params = "form", method = RequestMethod.POST)
public String submit(#Valid #ModelAttribute Entity entity, BindingResult result) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
entity.setCreatedBy(auth.getName());
if (result.hasErrors()) {
//Here the error of createdBy is null
return "entity/new";
} else {
entityService.save(entity);
return "redirect:/entity/list";
}
}
the entity class:
#Entity
#Table(name = "TABLE_X")
public class Entity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#NotNull
#Column(name = "primary_key")
private String primaryKey;
#NotNull
#Column(name = "created_by")
private String createdBy;
//getters and setter
}
I need set the value of createdBy in controller but always show "may not be null" in view.
Please help.
Spring MVC 4, Hibernate Validator 5, Database Oracle 11g
You entity object is validated before Spring MVC invokes the submit() method. The result object is created at the same time. This line:
entity.setCreatedBy(auth.getName());
has absolutely no effect on the outcome of result.hasErrors().

Resources