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);
}
Related
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
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
I have the following method skeleton in a Spring MVC application:
#RequestMapping(value = "/activateMember/{token}", method = RequestMethod.GET, produces = "text/html")
public String activateMember(#PathVariable("token") String token) {
...
}
I am trying to display an error message if the token is invalid for some reason. However I have no ModelAttribute in the method arguments and I don't really want one. But of course I can't use an Errors or BindingResults argument because of the absence of a ModelAttribute and its corresponding form.
So my question is:
what is the recommended way to display an error message given the above method signature and without introducing a ModelAttribute?
If the String you've returned from the method is a viewname (Spring default) then simply create a view for this case and do like:
#RequestMapping()
public String activateMember(#PathVariable("token") String token) {
if(checkToken(token)){
doProcess();
return "userprofile";
} else {
return "badtoken"
}
}
In more complicated case you may have a hierarchy of exceptions, related to bad tokens. (Token is expired, token is just incorrect and so on). You can register an #ExceptionHandler in the same controller:
#RequestMapping()
public String activateMember(#PathVariable("token") String token) {
return activate(token); // This method may throw TokenException and subclasses.
}
#ExceptionHandler(TokenException.class)
public ModelAndView tokenException(TokenException e){
// some code
return new ModelAndView("badtoken", "exception", e);
}
Situation
I'm trying to group the code that logs the exceptions and render a nice view in a few methods. At the moment the logic is sometime in the #RequestHandler itself (in the a catch block), othertimes is delegated to an utility class (that works but moves the logic away from the place where the exception is thrown).
Spring's #ExceptionHandler seemed the way to group everything in one place (the controller itself or a parent) and get rid of some code (no need to put logic in the try-catch and no need for an utility class)... until I realized that an #ExceptionHandler methods won't have the ModelMap or BindingResult parameters autowired. Currently those objects are used to render the view with a sensible error message and we want to log some information contained in these objects as well.
Question
Why Spring doesn't not support method arguments such ModelMap or BindingResult for the #ExceptionHandler? What is the rationale behind it?
Possible solution
In the Spring source code (3.0.5) the arguments for the method are resolved in the HandlerMethodInvoker.invokeHandlerMethod. A an exception thrown by the request handler is caught there and re-thrown. The #ExceptionHandler and it's parameters are resolved elsewhere. As a workaround I thought to check if the Exception implements an hypothetical "ModelAware" or "BindingResultAware" interface, and in that case set Model and BindingResult attributes before re-throwhing it.
How does it sound?
As stated before you can raise an exception wrapping a binding result object in some method of your controller:
if (bindingResult.hasErrors()) {
logBindingErrors(bindingResult);
//return "users/create";
// Exception handling happens later in this controller
throw new BindingErrorsException("MVC binding errors", userForm, bindingResult);
}
With your exception defined as illustrated here:
public class BindingErrorsException extends RuntimeException {
private static final Logger log = LoggerFactory.getLogger(BindingErrorsException.class);
private static final long serialVersionUID = -7882202987868263849L;
private final UserForm userForm;
private final BindingResult bindingResult;
public BindingErrorsException(
final String message,
final UserForm userForm,
final BindingResult bindingResult
) {
super(message);
this.userForm = userForm;
this.bindingResult = bindingResult;
log.error(getLocalizedMessage());
}
public UserForm getUserForm() {
return userForm;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
Next you just have to extract the required information from the raised then caught exception. Here assuming you have a suitable exception handler defined on your controller. It might be in a controller advice instead or even elewhere. See the Spring documentation for suitable and appropriate locations.
#ExceptionHandler(BindingErrorsException.class)
public ModelAndView bindingErrors(
final HttpServletResponse resp,
final Exception ex
) {
if(ex instanceof BindingErrorsException) {
final BindingErrorsException bex = (BindingErrorsException) ex;
final ModelAndView mav = new ModelAndView("users/create", bex.getBindingResult().getModel());
mav.addObject("user", bex.getUserForm());
return mav;
} else {
final ModelAndView mav = new ModelAndView("users/create");
return mav;
}
}
I ran into to same problem a while ago. The ModelMap or BindingResult are explicitly not listed as supported argument types in the JavaDocs of #ExceptionHandler, so this must have been intentional.
I reckon the reason behind it being that throwing exceptions in general could leave your ModelMap in an inconsistent state. So depending on your situation you might consider
Explicitly catch the exception to tell Spring MVC that you know what you're doing (you could use the Template pattern to refactor exception handling logic into one single place)
If you're in control of the exception hierarchy you could hand over the BindingResult to the exception and extract it from the exception later for rendering purposes
Not throw an exception in the first place, but use some result code (just like BeanValidation does for example)
HTH
To improve the first answer:
#ExceptionHandler(value = {MethodArgumentNotValidException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public VndErrors methodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) {
List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
List<ObjectError> globalErrors = ex.getBindingResult().getGlobalErrors();
List<VndError> errors = new ArrayList<>(fieldErrors.size() + globalErrors.size());
VndError error;
for (FieldError fieldError : fieldErrors) {
error = new VndError(ErrorType.FORM_VALIDATION_ERROR.toString(), fieldError.getField() + ", "
+ fieldError.getDefaultMessage());
errors.add(error);
}
for (ObjectError objectError : globalErrors) {
error = new VndError(ErrorType.FORM_VALIDATION_ERROR.toString(), objectError.getDefaultMessage());
errors.add(error);
}
return new VndErrors(errors);
}
There is already MethodArgumentNotValidException has already a BindingResult object, and you can use it, if you don't need to create an specific exception for this purpose.
I had the same problem to "add" FunctinalException to ourthe BindingResult
To resolve it, we use aop, if the controller method throws a runtime exception (or the one you want),
the aop catch it and update the bindingresult or model (if they are args of the method).
The method has to be annoted with a specific annotation containing the error path (configurable for specific exception if necessary).
It is not the best way because developer must not forget to add args that he don't use in its method but Spring does not provide a simple system to do this need.
I've wondered this too.
In order to handle bean validation in a way that allows for a non-global error view to display any ConstraintViolationExceptions that may be thrown, I opted for a solution along the lines of what #Stefan Haberl proposed:
Explicitly catch the exception to tell Spring MVC that you know what you're doing (you could use the Template pattern to refactor exception handling logic into one single place)
I created a simple Action interface:
public interface Action {
String run();
}
And an ActionRunner class which does the work of ensuring ConstraintViolationExceptions are handled nicely (basically the messages from each ConstraintViolationException is simply added to a Set and added to the model):
public class ActionRunner {
public String handleExceptions(Model model, String input, Action action) {
try {
return action.run();
}
catch (RuntimeException rEx) {
Set<String> errors = BeanValidationUtils.getErrorMessagesIfPresent(rEx);
if (!errors.isEmpty()) {
model.addAttribute("errors", errors);
return input;
}
throw rEx;
}
}
}
Java 8 makes this pretty nice to run within the controller action method:
#RequestMapping(value = "/event/save", method = RequestMethod.POST)
public String saveEvent(Event event, Model model, RedirectAttributes redirectAttributes) {
return new ActionRunner().handleExceptions(model, "event/form", () -> {
eventRepository.save(event);
redirectAttributes.addFlashAttribute("messages", "Event saved.");
return "redirect:/events";
});
}
This is to wrap up those action methods for which I'd like to explicitly handle exceptions that could be thrown due to Bean Validation. I still have a global #ExceptionHandler but this deals with only "oh crap" exceptions.
Actually it does, just create an #ExceptionHandler method for MethodArgumentNotValidException.
That class gives you access to a BindingResult object.
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);
}
}