Spring Boot (MVC) keeping object information to pass it to further URLs - spring-mvc

I'm trying to do my application that is dedicated for managing patients data base by doctors and have some problems with keeping information about object that is once send by post request method. I want them to be remembered in the URL. I tried to do something with #SessionAttributes but I don't think i do understand it well.
Here's my controller:
#Controller
#SessionAttributes("loggedInPersonelID")
#RequestMapping
public class PatientManagerController {
#Autowired
private PatientService patientService;
#Autowired
private PersonelService personelService;
private Personel getLoggedInPersonel(String personelID) {
return personelService.getPersonel(personelID);
}
#GetMapping
public ModelAndView getLoginView() {
ModelAndView mav = new ModelAndView("login-view");
mav.addObject("personel", new Personel());
return mav;
}
Method post passes logged in user to next URL /user={id} (used RedirectAttributes)
#PostMapping
public String loginUser(#ModelAttribute("personel") Personel personel,
RedirectAttributes redirectAttrs,
Model model) {
Personel loggedInPersonel = getLoggedInPersonel(personel.getPersonelID());
model.addAttribute("loggedInPersonelID", loggedInPersonel.getPersonelID());
if (loggedInPersonel != null) {
if (loggedInPersonel.getPassword().equals(personel.getPassword())) {
redirectAttrs.addAttribute("id", loggedInPersonel.getPersonelID());
return "redirect:/user={id}";
} else {
model.addAttribute("errorMessage", "Invalid credentials!");
return "login-view";
}
} else {
model.addAttribute("errorMessage", "User with given ID does not exist");
return "login-view";
}
}
Here's my get method that catches the view for logged in user. URL works here since the model was passed in previous post method. I've got something like /user=john-smith-123
#GetMapping("/user={id}")
public ModelAndView getUserMainView(#PathVariable("id") String personelID) {
ModelAndView mav = new ModelAndView("personel-main-view");
Personel loggedInPersonel = getLoggedInPersonel(personelID);
mav.addObject("personelOccupation", loggedInPersonel.getOccupation());
mav.addObject("personelName", loggedInPersonel.getName());
mav.addObject("personelSurname", loggedInPersonel.getSurname());
return mav;
}
However the next page doesn't remember the user's id anymore. I thought that passing it to the model's attribute with the same name as determined in #SessionAttributes("loggedInPersonelID") the information will be remembered.
#GetMapping("/user={id}/patients")
public ModelAndView getPatientsView(#PathVariable("id") String personelID) {
ModelAndView mav = new ModelAndView("patients-view");
Personel loggedInPersonel = getLoggedInPersonel(personelID);
mav.addObject("loggedInPersonelID", loggedInPersonel.getPersonelID());
mav.addObject("list", patientService.getPersonelsList(loggedInPersonel));
return mav;
}
The outcome in the URL: user=$%7BloggedInPersonelID%7D/patients and error There was an unexpected error (type=Internal Server Error, status=500).
No message available
Here's the link in a personel-main-view view that should move me to desired page
<a th:href="#{/user=${loggedInPersonelID}/patients}">My patients</a>
So how do I do that? Sorry for the messy code. Is this matter more complicated than it looks like? Is there something deeper I am missing?
PS. I am working with thymeleaf

try this:
<a th:href="#{/user=__${loggedInPersonelID}__/patients}">My patients</a>
this works as shown here

Related

Spring Model - "Model object must not be null"

A method in one of my Spring controller class,
#RequestMapping(value = "/products/{productId}/specifications", method = RequestMethod.GET)
public String setup(#PathVariable("productId") Integer pid, Model m) {
//...
m.addAttribute(foo); <-- error
return "my-page";
}
After I got an error message "Model object must not be null", I change the method signature as shown in the following:
public ModelAndView setup(#PathVariable("productId") Integer pid) {
//...
ModelAndView mv = new ModelAndView("my-page");
mv.addObject(foo); <-- error
return mv;
}
I was able to run the modified code once. But I got the same error on ModelAndView. I have used Spring MVC for many years. That is my first time having this problem. What is the cause?
I use Spring 4.0.6.RELEASE.
Although you have not provided the code that shows what the foo reference points to, it is safe to assume it is a null reference.
I took a look at the Project code on Github, and it is clear what happening here.
The ModelAndView#addObject(Object) method delegates to the ModelMap#addAttribute(Object) method, which asserts that the provided Object is not null, using the exact message your question is asking about.
ModelAndView method:
public ModelAndView addObject(Object attributeValue) {
getModelMap().addAttribute(attributeValue);
return this;
}
ModelMap method:
public ModelMap addAttribute(Object attributeValue) {
Assert.notNull(attributeValue, "Model object must not be null");
if (attributeValue instanceof Collection && ((Collection<?>) attributeValue).isEmpty()) {
return this;
}
return addAttribute(Conventions.getVariableName(attributeValue), attributeValue);
}

#ExceptionHandler with parameters not working

I am trying to capture all exceptions of some class in my Controller class. It works fine when
I define it like this:
#ExceptionHandler(NoSearchResultException.class)
public String handleNoSearchResultException() {
return "someView";
}
But not if I add any parameters:
#ExceptionHandler(NoSearchResultException.class)
public String handleNoSearchResultException(Exception e) {
return "someView";
}
What could possibly be happening? Also, I've read #ExceptionHandler does not support Model arguments, so how would I pass a parameter (like the error message for instance) to the view in order to offer a dynamic error page?
To pass a parameter to the view I would create a custom Exception class in which you can store any required model parameters (such as error messages). Then in #ExceptionHandler method you can extract those model parameters and make them available in the view. For example:
class RequestException extends RuntimeException {
...
public void setErrorMessages(List<String> errorMsgs) {
this.errorMessages = errorMsgs
}
...
}
#ExceptionHandler(RequestException.class)
public ModelAndView handleNoSearchResultException(RequestException ex) {
ModelAndView mav = new ModelAndView("someView");
mav.addObject("errors", ex.getErrorMessages()); //fetch error messages
return mav;
}
As for parameters, try specifying NoSearchResultException as method parameter instead of it's Exception superclass.
EDIT:
Had a bug in 2nd example return value.
I Solved the problem by passing the custom arguments in request itself.
code is as below :
Controller
#RequestMapping(method = RequestMethod.GET, value = "/exception2")
public String getException1(ModelMap model, #CRequestParam("p") String p, HttpServletRequest request) {
System.out.println("Exception 2 " + p);
request.setAttribute("p", p);
throw new CustomGenericException("1", "2");
}
Exception Handler
#ExceptionHandler(CustomGenericException.class)
public ModelAndView handleCustomException(CustomGenericException ex, HttpServletRequest request) {
ModelAndView model2 = new ModelAndView("error/generic_error");
model2.addObject("exception", ex);
System.out.println(request.getAttribute("p"));
System.out.println("CustomGenericException ");
return model2;
}
here is Sackoverflow question and its answer and
Complete source code is available at git

Spring-MVC 3.1: Forwarding A Request From One Controller Function To Another

I'm using Spring 3.1. I have a controller function that takes in a command object ( a data holder ) submitted via a FORM and does some processing :
#RequestMapping(value = "/results", method = RequestMethod.POST)
public String toResultsScreen(#ModelAttribute("ssdh") SearchScreenDataHolder ssdh,
BindingResult bindingResult,
ModelMap model,
HttpSession session) {
if (bindingResult.hasErrors()) {
logger.debug("Error returning to /search screen");
return "search";
}
netView = "results";
// do stuff
return nextView;
} // end function
Some user would like to programmatically make GET links to obtain information from our site and I would like to set up another handler that would handle that request. It would create a new installation of that the command object ( ssdh ) and populate it with the parameters sent via the GET request. Then it would pass it on to the handler above. Something like this:
#RequestMapping(value = "/pubresult")
public String toPublicResultsScreen(ModelMap model,
HttpSession session,
#RequestParam (required=true) String LNAME,
#RequestParam (required=false)String FNAME){
Search search = new Search(usertype);
// Capture the search parameters sent by HTTP
ssdh.setLast_name(LNAME);
ssdh.setFirst_name(FNAME);
// To Do: "forward this data holder, ssdh to the controller function quoted first
return nextView;
} // end function
My question is how can I forward my command/data holder object to the first controller function such that I don't have to alter the code to the first controller function in any way?
You can use RedirectAttributes object which was introduced in Spring MVC 3.1 and populate it with data you want to keep for redirection. It called PRG (POST/Redirect/GET) pattern.
#RequestMapping(value="/saveUserDetails.action", method=RequestMethod.POST)
public String greetingsAction(#Validated User user,RedirectAttributes redirectAttributes){
//setting attributes
redirectAttributes.addFlashAttribute("firstName", user.getFirstName());
redirectAttributes.addFlashAttribute("lastName", user.getLastName())
return "redirect:success.html";
}
I wrote some technical article regarding how to use it. I believe it will give you more details:
http://www.tikalk.com/java/redirectattributes-new-feature-spring-mvc-31
You should be able to set the ssdh in a ModelAttribute and simply forward it back, this way, the RequestDispatcher should be able to map it back to the /results handler:
#RequestMapping(value = "/pubresult")
public String toPublicResultsScreen(ModelMap model,
HttpSession session,
#RequestParam (required=true) String LNAME,
#RequestParam (required=false)String FNAME, Model model){
Search search = new Search(usertype);
// Capture the search parameters sent by HTTP
ssdh.setLast_name(LNAME);
ssdh.setFirst_name(FNAME);
model.addAttribute("ssdh", ssdh);
return "forward:/results";
}
Use
org.springframework.web.servlet.view.RedirectView
class from spring package to redirect to different page in spring MVC controller. The Baeldung blog page has more details
Sample code:
#RequestMapping(value = "/", method = RequestMethod.GET)
public RedirectView mainMethod() {
return new RedirectView("/login");
}
#RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView mainLogin() {
ModelAndView model = new ModelAndView("login");
return model;
}

controller chaing in spring 3

i have to call one controller on the basis of first controller i have to call another controller ......
but this is not working in spring 3 mvc........
#Controller
public class ajaxContoller {
#RequestMapping(value="/mmiFacade",method=RequestMethod.POST)
public #ResponseBody String mmiFacade(#RequestParam String sType){
String forwardName = "";
if (sType.equalsIgnoreCase("Pincode")) {
forwardName = "forward:/pincodeAction";
} else if (sType.equalsIgnoreCase("Locality")) {
forwardName = "forward:/localityAction";
} else if (sType.equalsIgnoreCase("Patient")) {
forwardName = "forward:/patientAction";
} else if (sType.equalsIgnoreCase("Dlhdata")) {
forwardName = "forward:/Dlhdata";
}
return forward;
}
#RequestMapping(value="/pincodeAction",method=RequestMethod.POST)
public #ResponseBody String ajax(){
return "hiii";
}
#RequestMapping(value="/localityAction",method=RequestMethod.POST)
public #ResponseBody String ajax1(){
return "hiii1";
}
}
You should return modelandview object. view name starting with "forward:/" will do the job, otherwise Spring does not even try to interpret the response.
Another option to implement a switch and to invoke other mapping as simple call to anther java function.
Returning a String containing the view name does exactly the same thing as returning a ModelAndView object with the view name set to a String. If you just return a String, Spring internally creates a ModelAndView and set the view name to the value of the String.
In your example, you should not annotate the mmiFacade method with #ResponseBody. Using #ResponseBody bypasses the view resolution process, which is where the "forward:" and "redirect:" prefixes in view names are detected and processed.

How to manage validation in GET form and submit POST form?

the enviroment is Spring 3.0 with new function Vallidation.
I create an annotated controller (ResetUserPasswordController) which manages a showForm on HTTP.GET and the submit form on HTTP.POST. The function is a reset user password requested by email : the user access previously to another form, where i fill is email address and a recaptcha control, if recaptcha is correct, the user receive a mail with a link which contains a paramter. The two methods (on HTTP.GET, and HTTP.POST) have two different command bean have different paramters(i choice two differents beans to manage the validation process in two diffent validators classes). Probably you are questioning : why do you define two differents commands? I have defined the following role : Every bussiness and basic (like notnull validation etc) validation process must be managed by a validator class which supports a specific command bean
I want to create the istance of command bean managed by the POST, in the GET method, but during some tests I realized that this can be not correct beacuse if the validation process goes bad, I have all errors on the input command which is different from which i'm gogin to put in the returned ModelAndView.
Someone has some suggestion to manage correctly this scenario?
#RequestMapping(method = RequestMethod.POST)
public ModelAndView processSubmit(#Valid #ModelAttribute("command") ResetUserPasswordCommand command, BindingResult result, HttpServletRequest request, HttpServletResponse response) {
getValidator().validate(command, result);
if (result.hasErrors()) {
// TODO : implements error page.
return new ModelAndView();
} else {
Map<String, Object> model = new HashMap<String, Object>();
try {
PasswordChangeRequest passwordChangeRequest = getUserService().findPasswordChangeRequest(command.getUuid());
getUserService().updateUserPassword(command.getUuid(), command.getPassword());
autoLogin(request, response, passwordChangeRequest.getAccount(), command.getPassword());
} catch (ApplicationThrowable aex) {
return new ModelAndView("responseKO", model);
}
return new ModelAndView("Home", model);
}
}
#RequestMapping(method = RequestMethod.GET)
public ModelAndView setupForm(#Valid #ModelAttribute("command") ResetUserPasswordFormCommand command, BindingResult result) {
getFormValidator().validate(command, result);
if (result.hasErrors()) {
// TODO : implements error page.
return new ModelAndView();
} else {
Map<String, Object> model = new HashMap<String, Object>();
ResetUserPasswordCommand resetUserPasswordCommand = new ResetUserPasswordCommand();
resetUserPasswordCommand.setUuid(command.getUuid());
model.put("command", resetUserPasswordCommand);
model.put("reCaptchaHTML", getReCaptchaService().getReCaptchaObjectNoSSL().createRecaptchaHtml(null, null));
return new ModelAndView("user/ResetUserPassword", model);
}
}

Resources