How can I check if the user have correctly submitted the previous form into a Spring MVC application that contemplate some steps? - spring

I am pretty new in Spring MVC and I have the following situation.
I am working on a Spring MVC application that implement a user registration process. The prcess is divided into 4 steps. In each step the user insert some information into a form that is submitted and that is handled by the related method into the controller class. Each of these controller method take the related command object that contains the information of the submitted form.
So I have something like this:
#Controller
public class RegistrazioneController {
// This is the first step and show a view that contain the first form:
#RequestMapping(value = "/registrationStep1")
public String registrationStep1(Model model) {
return "/registrazione/registration-step1";
}
#RequestMapping(value = "/registrationStep2", method = RequestMethod.POST)
public String registrationStep2(#ModelAttribute RegistrationStep1 registrationStep1, Model model) throws APIException {
.......................................................
.......................................................
.......................................................
return "/registrazione/registration-step2";
}
#RequestMapping(value = "/registrationStep3", method = RequestMethod.POST)
public String registrationStep3(#ModelAttribute RegistrationStep3 registrationStep3, Model model) throws APIException {
.......................................................
.......................................................
.......................................................
return "/registrazione/registration-step3";
}
// This method return the final view after the completation of the user registration:
#RequestMapping(value = "/registrationStep4", method = RequestMethod.POST)
public String registrationStep2(#ModelAttribute RegistrationStep4 registrationStep4, Model model) throws APIException {
.......................................................
PERFORM THE USER REGISTRATION
.......................................................
return "/registrazione/registration-step4";
}
}
So it works pretty fine. My problem is that the application have tho check that, when enter into a registration step, the previous steps are completed (the previous form was compiled and submitted).
So I think that I have to do something like this, for example: ** when enter into the registrationStep3() have to check if the command object of the previous registrationStep2() step method was correctly setted (it is valid), so it means that the user have completed the previous registration step.
The application have to prevent that the user try to acces the registration starting from a step without having complete the previous steps of the registration process.
What is the best way to implement this behavior?

I have worked in some Sap Hybris projects and this platform suggest to use the following process :
Step1Form, Step2Form and Step3Form, if you have first name and last name in your 1 step form you ll have the same in Step1Form class as attributes.
and for each class create a validator, in the next step controller u have to validate the previous step if it is not valid redirect the user to the previous step.
you already have RegistrationStep1, and RegistrationStep2 and RegistrationStep3
lets create a validator for RegistrationStep1 :
import org.apache.commons.validator.routines.EmailValidator;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
#Component(value = "registrationStep1Validator")
public class RegistrationStep1Validator implements Validator
{
#Override
public boolean supports(final Class<?> aClass)
{
return RegistrationStep1.class.equals(aClass);
}
#Override
public void validate(final Object object, final Errors errors)
{
final RegistrationStep1 step1= (RegistrationStep1) object;
final String name = step1.getName();
final String email = step1.getEmail();
if (email.isEmpty or email == null)
{
errors.reject("email", "Email must not be blank or null");
}
if (name.isEmpty or name== null)
{
errors.reject("name", "Name must not be blank");
}
if (!EmailValidator.getInstance().isValid(email))
{
errors.reject("email", "Email must be valid");
}
}
}
//later in your controller
#RequestMapping(value = "/registrationStep2", method = RequestMethod.POST)
public String registrationStep2(#ModelAttribute RegistrationStep1 registrationStep1,final BindingResult bindingResult, Model model) {
registrationStep1Validator.validate(registrationStep1,bindingResult);
if (bindingResult.hasErrors())
{
return "/registrazione/registration-step1";
}
return "/registrazione/registration-step2";
}

Related

Spring MVC and Thymeleaf Prevent Entity Id Leak

I have Demand entity. I can update my entity without any problem but I think my approch have some security problem.
demandController
#RequestMapping(value = "/details/{id}", method = RequestMethod.POST)
public String updateDemand(#PathVariable("id") Long id, #Valid #ModelAttribute Demand demand, BindingResult result) {
if (result.hasErrors()) {
return "demandUpdateForm";
} else {
demand.setDemandId(id);
demandService.updateDemand(demand);
return "redirect:/demands";
}
}
serviceImpl
#Override
public Demand updateDemand(Demand demand) {
return demandRepository.save(demand);
}
form
<form id="vendorForm" th:action="#{/demands/details/__${demand.demandId}__}" th:object="${demand}" method="post" >
As you see I get DemandId from action. For example I want to update 5th id's demand and get the update form. Then I changed demandId via developer tools and click submit. If I modify id for example 2nd and form update my 2nd id demand not original the 5th one. How can I prevent this situation.
I think it would be better if you create unmanaged bean for this operations and will pass it as form backing bean.
public class DemandBean {
private Long id;
private String name;
...
// more fields
}
Controller :
#RequestMapping(value = "/details/update", method = RequestMethod.POST)
public String updateDemand(#Valid #ModelAttribute("demandBean") DemandBean demandBean, BindingResult result) {
if (result.hasErrors()) {
return "demandUpdateForm";
} else {
demandService.updateDemand(demandBean.getId(), demandBean.getName, ...);
return "redirect:/demands";
}
}
Service method :
#Override
public void updateDemand(Long id, String name, //etc) {
Demand d = id == null ? new Demand() : demandRepository.findOne(id);
d.setName(name);
// ...
// set other fields
return demandRepository.save(demand);
}
This approach helps you to avoid security leaks with passing id.

Spring #ExceptionHandler, how to preserve model attributes

Seems like #ExceptionHandler clears the model populated by the request handler that threw the exception. Consider the following scenario:
#Controller
public class GreetController {
#RequestMapping(value = "form", method = RequestMethod.GET)
public String showForm(#ModelAttribute("userInfoFormObject") UserInfoForm form) {
return "form";
}
#RequestMapping(value = "processform", method = RequestMethod.POST)
public String processForm(#Valid #ModelAttribute("userInfoFormObject") UserInfoForm form,
BindingResult errors)
throws RegisterFormException {
if (errors.hasErrors())
throw new RegisterFormException();
return "greet";
}
#ExceptionHandler(RegisterFormException.class)
public String registerFormException() {
return "form";
}
}
User inputs invalid data into a register form, RegisterFormException is thrown and exception handler takes user back to register form. Spring jstl tag library expects UserInfoForm object as an model attribute. However, exception handler creates new empty model. Is there way to preserve the populated model across exception handler, or is my only choice to return form view name in the request handler in the case of errors? Is the example solution considered as an anti pattern?

Strange Spring #SessionAttributes Behavior

I'm using #SessionAttributes on 2 controllers and am experiencing some very strange behavior. My first controller (ViewController) is simply a view controller that displays JSP pages. The other is a controller that handles Ajax requests (AjaxController). I have a session attribute that is simply an object that has a HashMap as a member. The object is a wrapper around the map. The map is populated from the database and put in the session, which displays fine via the ViewController. However, when I do a delete from the map via an ajax request (AjaxController) and refresh the page, ViewController SOMETIMES shows that the element is removed, yet other times the element is still there. Here's code snippets:
ViewController (the homepage simply displays the contents of the map contained by userSettings
#Controller
#SessionAttributes({"userSettings"})
public class ViewController {
#RequestMapping(value="/", method=RequestMethod.GET)
public String home(ModelMap model) {
UserSettings userSettings = (UserSettings) model.get("userSettings");
String userListenersJson = userSettings.toJson(); // for bootsrtapping the js on the front end
return "views/home";
}
}
AjaxController:
#Controller
#SessionAttributes({"userSettings"})
public class AjaxController {
#RequestMapping(value="/users/listeners/{externalId}", method=RequestMethod.DELETE)
public #ResponseBody
AjaxResponse<?> deleteListener(ModelMap model,
#PathVariable long externalId) {
UserSettings userSettings = (UserSettings) model.get("userSettings");
userSettings.removeSetting(externalId);
return new AjaxResponse<String>(null, true);
}
}
Am I using #SessionAttributes wrong here? Why would this work sometimes and not others? I've also tried putting all of the view and ajax functionality in the same controller and experienced the same behavior.
Thanks for any help!
EDIT:
I've refactored my code a bit to use the UserPrincipal via springsecurity. My understanding is that this object is stored in the session. Regardless, I'm seeing exactly the same behavior.
Here's the UserPrincipal constructor that populates the user settings map. I've set breakpoints here to ensure that the correct listenerDBOs are set - they are, every time. This is the only time the listeners get set from the db into the UserSettings object in CustomUserPrincipal. All other adds/removes are done via the controllers (quick aside: adds never fail... only removes):
public CustomUserPrincipal(UserDBO userDBO) {
// set UserSettings obj
UserSettingsAdapter.addListeners(userDBO.getUserListenerDBOs(), userSettings);
}
The UserSettings object itself:
public class UserSettings implements Serializable {
private static final long serialVersionUID = -1882864351438544088L;
private static final Logger log = Logger.getLogger(UserSettings.class);
private Map<Long, Listener> userListeners = Collections.synchronizedMap(new HashMap<Long, Listener>(1));
// get the listeners as an arraylist
public List<Listener> userListeners() {
return new ArrayList<Listener>(userListeners.values());
}
public Map<Long, Listener> getUserListeners() {
return userListeners;
}
public Listener addListener(Listener listener) {
userListeners.put(listener.getId(), listener);
return listener;
}
// I'm logging here to try and debug the issue. I do see the success
// message each time this function is called
public Listener removeListener(Long id) {
Listener l = userListeners.remove(id);
if (l == null) {
log.info("failed to remove listener with id " + id);
} else {
log.info("successfully removed listener with id " + id);
}
log.info("Resulting map: " + userListeners.toString());
log.info("Map hashcode: " + userListeners.hashCode());
return l;
}
public Listener getListener(long id) {
return userListeners.get(id);
}
}
This is the helper function in the UserSettingsAdapter class that adds to the UserSettings object, called from CustomUserDetails constructor:
public static void addListeners(Set<UserListenerDBO> userListeners, UserSettings userSettings) {
for (UserListenerDBO userListenerDBO : userListeners) {
if (userListenerDBO.isActive()) {
addListener(userListenerDBO, userSettings);
}
}
}
I've also changed the controller code to user the CustomUserPrincipal object instead of #SessionAttributes:
In ViewController:
#RequestMapping(value="/", method=RequestMethod.GET)
public String home(ModelMap model) {
CustomUserPrincipal userPrincipal = authenticationHelpers.getUserPrincipal();
UserSettings userSettings = userPrincipal.getUserSettings();
String userListenersJson = userSettings.toJson();
return "views/home";
}
In AjaxController:
#RequestMapping(value="/users/listeners/{externalId}", method=RequestMethod.DELETE)
public #ResponseBody
AjaxResponse<?> deleteListener(ModelMap model,
#PathVariable long externalId) {
CustomUserPrincipal userPrincipal = authenticationHelpers.getUserPrincipal();
UserSettings userSettings = userPrincipal.getUserSettings();
userSettings.removeListener(externalId);
return new AjaxResponse<String>(null, true);
}
I hope this helps shed some light on the issue!
I ran into a similar problem with #SessionAttributes. A controller had a #SessionAttributes annotation at the class level, and one of the methods handled POST requests, and included an instance of the session-managed object as an argument. This instance was saved to the database, but was re-used by subsequent requests, causing some data corruption. We had to add another method argument of type SessionStatus, and call SessionStatus.setComplete(). This caused the instance to be removed from the session, and prevented reuse and corruption. So try adding a SessionStatus instance to your controllers' handler methods, and invoke setComplete() where appropriate.
EDIT: I accidentally referenced the getter isComplete() in my initial answer; I meant to reference the setter setComplete().
#SessionAttributes is specific to a Controller and is not shared among several Controllers.
Instead, consider using manually session.setAttribute (class HttpSession).
You should have a look here : http://beholdtheapocalypse.blogspot.fr/2013/01/spring-mvc-framework-sessionattributes.html

Nested Velocity template with Spring formView

I have a Spring app that I'd like to add a login feature to. I'd like to put the login form in the header of the site. This means that it'll be included on several pages. When defining the controller that the form submits to, what do I specify as the formView?
Is it possible to specify the login template that's included in header (that's included in each head :-)) as the formView?
Thanks for the help. If anything is unclear than I'm happy to provide more details or show code.
Nevermind. I realized that it doesn't matter whether the Velocity template is included in another file. I added this to the template:
<form method="POST">
#springBind("credentials.*")
and my controller looks like this:
#Controller
public class SplashController implements Serializable {
protected final Log logger = LogFactory.getLog(getClass());
private static final long serialVersionUID = 7526471155622776147L;
#ModelAttribute("credentials")
public LoginCredentials getFormBean() {
return new LoginCredentials();
}
#RequestMapping(method = RequestMethod.GET)
public String showForm() {
logger.info("In showForm method of SplashController");
return "splash";
}
#RequestMapping(method = RequestMethod.POST)
public ModelAndView onSubmit(LoginCredentials credentials, BindingResult bindingResult) {
logger.info("In onSubmit method of SplashController");
logger.info("Username = " + credentials.getUsername());
logger.info("Password = " + credentials.getPassword());
ModelAndView modelAndView = new ModelAndView("home");
return modelAndView;
}
}
and it works.

Spring Framework 3 and session attributes

I have form object that I set to request in GET request handler in my Spring controller. First time user enters to page, a new form object should be made and set to request. If user sends form, then form object is populated from request and now form object has all user givern attributes. Then form is validated and if validation is ok, then form is saved to database. If form is not validated, I want to save form object to session and then redirect to GET request handling page. When request is redirected to GET handler, then it should check if session contains form object.
I have figured out that there is #SessionAttributes("form") annotation in Spring, but for some reason following doesnt work, because at first time, session attribute form is null and it gives error:
org.springframework.web.HttpSessionRequiredException: Session attribute 'form' required - not found in session
Here is my controller:
#RequestMapping(value="form", method=RequestMethod.GET)
public ModelAndView viewForm(#ModelAttribute("form") Form form) {
ModelAndView mav = new ModelAndView("form");
if(form == null) form = new Form();
mav.addObject("form", form);
return mav;
}
#RequestMapping(value="form", method=RequestMethod.POST)
#Transactional(readOnly = true)
public ModelAndView saveForm(#ModelAttribute("form") Form form) {
FormUtils.populate(form, request);
if(form.validate())
{
formDao.save();
}
else
{
return viewForm(form);
}
return null;
}
It throws Exception if controller called first time even though added #SessionAttributes({"form"}) to class. So add following populateForm method will fix this.
#SessionAttributes({"form"})
#Controller
public class MyController {
#ModelAttribute("form")
public Form populateForm() {
return new Form(); // populates form for the first time if its null
}
#RequestMapping(value="form", method=RequestMethod.GET)
public ModelAndView viewForm(#ModelAttribute("form") Form form) {
ModelAndView mav = new ModelAndView("form");
if(form == null) form = new Form();
mav.addObject("form", form);
return mav;
}
#RequestMapping(value="form", method=RequestMethod.POST)
#Transactional(readOnly = true)
public ModelAndView saveForm(#ModelAttribute("form") Form form) {
// ..etc etc
}
}
The job of #SessionAttribute is to bind an existing model object to the session. If it doesn't yet exist, you need to define it. It's unnecessarily confusing, in my opinion, but try something like this:
#SessionAttributes({"form"})
#Controller
public class MyController {
#RequestMapping(value="form", method=RequestMethod.GET)
public ModelAndView viewForm(#ModelAttribute("form") Form form) {
ModelAndView mav = new ModelAndView("form");
if(form == null) form = new Form();
mav.addObject("form", form);
return mav;
}
#RequestMapping(value="form", method=RequestMethod.POST)
#Transactional(readOnly = true)
public ModelAndView saveForm(#ModelAttribute("form") Form form) {
// ..etc etc
}
}
Note that the #SessionAttributes is declared on the class, rather than the method. You can put wherever you like, really, but I think it makes more sense on the class.
The documentation on this could be much clearer, in my opinion.
if there is no defined session object so I think it's gonna be like this:
#SessionAttributes({"form"})
#Controller
public class MyController {
#RequestMapping(value="form", method=RequestMethod.GET)
public ModelAndView viewForm() {
ModelAndView mav = new ModelAndView("form");
if(form == null) form = new Form();
mav.addObject("form", form);
return mav;
}
#RequestMapping(value="form", method=RequestMethod.POST)
#Transactional(readOnly = true)
public ModelAndView saveForm(#ModelAttribute("form") Form form) {
// ..etc etc
}
}
#Controller
#SessionAttributes("goal")
public class GoalController {
#RequestMapping(value = "/addGoal", method = RequestMethod.GET)
public String addGoal(Model model) {
model.addAttribute("goal", new Goal(11));
return "addGoal";
}
#RequestMapping(value = "/addGoal", method = RequestMethod.POST)
public String addGoalMinutes(#ModelAttribute("goal") Goal goal) {
System.out.println("goal minutes " + goal.getMinutes());
return "addMinutes";
}
}
On page addGoal.jsp user enters any amount and submits page. Posted amount is stored in HTTP Session because of
#ModelAttribute("goal") Goal goal
and
#SessionAttributes("goal")
Without #ModelAttribute("goal") amount entered by user on addGoal page would be lost
I'm struggling with this as well. I read this post and it made some things clearer:
Set session variable spring mvc 3
As far as I understood it this basically says:
that Spring puts the objects specified by #SessionAttributes into the session only for the duration between the first GET request and the POST request that comes after it. After that the object is removed from the session. I tried it in a small application and it approved the statement.
So if you want to have objects that last longer throughout multiple GET and POST requests you will have to add them manually to the HttpSession, as usual.

Resources