Manually handle error validation in Spring annotated controller - java

I'm trying to update a Spring controller to use annotations for a relatively simple 'change password' page. The only fields on the page are 'password' and 'confirm password'. When the form is submitted, it calls to a webservice to do the actual changing of the password. That webservice may return a InvalidPasswordException based upon password rules run within that service. So I want to catch the exception, then add an error message to the view to show up next to the 'password' field. The velocity code is already written using #springShowErrors, so I want to add the error in a way that in can be read in by that tag.
Here is my controller:
#Controller
#RequestMapping("/edit-password.ep")
public class EditPasswordFormControllerImpl {
#Autowired
private CustomerService customerService;
#Autowired
private CustomerSessionService customerSessionService;
#RequestMapping(method = RequestMethod.POST)
protected ModelAndView onSubmit(#ModelAttribute("editPasswordFormBean") EditPasswordFormBeanImpl editPasswordFormBean, BindingResult errors, HttpServletRequest request) throws EpWebException {
String nextView = "redirect:/manage-account.ep";
final CustomerSession customerSession = (CustomerSession) request.getSession().getAttribute(WebConstants.CUSTOMER_SESSION);
final Customer customer = customerSession.getShopper().getCustomer();
try {
CustomerInfo customerInfo = new CustomerInfo();
customerInfo.setCustomerId(customer.getUserId());
customerInfo.setPassword(editPasswordFormBean.getPassword());
UpdateAccountServiceRequest updateRequest = new UpdateAccountServiceRequest();
updateRequest.setClientId(CLIENT_ID);
updateRequest.setCustomerInfo(customerInfo);
//this is the webservice call that could throw InvalidPasswordException
customerService.updateUserAccount(updateRequest);
} catch (InvalidPasswordException e) {
// This is where I'm not sure what to do.
errors.addError(new ObjectError("password", e.getMessage()));
nextView = "edit-password.ep";
} catch (ServiceException e) {
throw new EpWebException("Caught an exception while calling webservice for updating user", e);
}
return new ModelAndView(nextView);
}
#RequestMapping(method = RequestMethod.GET)
protected String setupForm(ModelMap model) {
EditPasswordFormBean editPasswordFormBean = new EditPasswordFormBeanImpl();
model.addAttribute("editPasswordFormBean", editPasswordFormBean);
return "account/edit-password";
}
}
And here is a snippet of my velocity template:
<fieldset>
<legend>#springMessage("editPassword.editPasswordTitle")</legend>
<table border="0" cellspacing="0" cellpadding="3">
<colgroup>
<col width="150">
<col width="*">
</colgroup>
<tr>
<td colspan="2">
<br />
<strong>#springMessage("editPassword.changePassword")</strong>
</td>
</tr>
<tr>
<td align="right">#springMessage("editPassword.password")</td>
<td>
#springFormPasswordInput("editPasswordFormBean.password" "maxlength='100'")
#springShowErrors("<br>" "req")
</td>
</tr>
<tr>
<td align="right">#springMessage("editPassword.confirmPassword")</td>
<td>
#springFormPasswordInput("editPasswordFormBean.confirmPassword" "maxlength='100'")
#springShowErrors("<br>" "req")
</td>
</tr>
</table>
</fieldset>
I'm not quite sure what I should do when I catch the exception. What I currently have doesn't work. It returns to the edit-password page, but no error displays. I've read about HandleExceptionResolver, but even if I use that, I'm still not sure how to get the error to display on the view.
Any help is greatly appreciated!

Jeff, its just a guess, if you see the controller you have the RequestMapping("/edit-password.ep"), so when there is an error scenario your next view is "edit-password.ep", so it will come to this controller and it will be consdiered as a get request to the controller. But in your GET mapping method you are always creating a new EditPasswordBean and sending back to the back. If you run the server in debug mode and keep a break point in setUpForm method you will why the errors have been swallowed. Try to give specific mappings for get and post to avoid such issues. Ideally you should a Validator defined and it should be registered in your initBinder method. Check out this link http://static.springsource.org/spring/docs/current/spring-framework-reference/html/validation.html
Hope it helps.

Related

Spring MVC: #PathVariable form

I have a data base which contains some items. I want to create a form which edits item with some id. I did it, form opens fine. Adress is /itemproject/edit_item/{id} Problems start when I'm trying to activate POST method. Instead of directing me to page with item list (/itemproject/view_items) programm sends me to /itemproject/edit_item/edit_item. itemproject is context path (for example).
#RequestMapping(value = "/edit_item/{id}", method = RequestMethod.GET)
public String editItem(#PathVariable("id") Integer id, Model model) {
Item item;
item = dbService.findItem(item).get(0);
model.addAttribute("item", item);
return "edit_item";
}
#RequestMapping(value = "/edit_item/{id}", method = RequestMethod.POST)
public String editItemComplete(#PathVariable("id") Integer id, #ModelAttribute("item") Item item, Model model) {
dbService.updateItem(item);
model.addAttribute("items",dbService.findAllItems());
return "view_items";
}
dbService works with data base.
I want that programm sent me to list of all items after ediding chosen item and updating it in database.
Here is example of edit form (url: /itemproject/edit_item/{id}
<spring:url value="edit_item" var="formURL"/>
<form:form action="${formURL}"
method="post" cssClass="col-md-8 col-md-offset-2"
modelAttribute="item"
>
<div class="form-group">
<label for="item-stuff">Stuff</label>
<form:input id="item-stuff"
cssClass="form-control"
path="stuff"/>
</div>
<button type="submit" class="btn btn-default">Edit item</button>
</form:form>
This is how my item list page looks (url: /itemproject/view_items)
<body>
<table class="table table-hover">
<tbody>
<tr>
<th>Stuff</th>
</tr>
<c:forEach items="${items}" var="item">
<tr>
<td>${item.stuff}</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
From Spring docs:
In Spring MVC you can use the #PathVariable annotation on a method
argument to bind it to the value of a URI template variable
That means that #PathVariable annotation is suitable when you use the GET method because when you use GET method you can pass your query string.
Instead, try to use #RequestBody in order to try to bind your POST HTTP body message to your parameter
For example:
#RequestMapping(value = "/edit_item", method = RequestMethod.POST)
public String editItemComplete(#RequestBody String body) {
//in here you'll have to pull the body content
return "view_items";
}
Let's say that you're sending an Integer id on HTTP POST body, then you can pull the data from the body like this:
#RequestMapping(value = "/edit_item", method = RequestMethod.POST)
public String editItemComplete(#RequestBody String body) {
ObjectMapper objectMapper = new ObjectMapper();
try {
idJson = objectMapper.readTree(body).path("id").asInt();
} catch (IOException e) {
e.printStackTrace();
}
return "view_items";
}
assuming that you're sending json from client to service.
Rather than loading the items and returning the view_items template, you can return "redirect:/itemproject/view_items" and that will cause your handler for view_items to be invoked, which will load the items etc.

passing parameter using JSP URL to controller in spring mvc, returns 404

First I am getting a list of objects on JSP page, each has an hyperlink(anchor), then on click of anchor I have to send index of the list(surveyId which is also originally coming in surveyList) to controller but the controller is not getting called. It returns 404.
<body>
Create a new survey
<table border="1" cellpadding="5" cellspacing="5">
<tr>
<td><b>Existing Surveys</b></td>
</tr>
<c:forEach var="survey" items="${surveyList}" varStatus="status">
<tr>
<td>${survey.surveyTitle}</td>
<td><a id="byParameter"
href="<c:url value='/home.htm/${surveyList[status.index].surveyId}' />">Share</a>
</td>
</tr>
</c:forEach>
</table>
</body>
URL getting generated is what is need, for example: /project/home.htm/15
This is how the controller looks, I have tried with both #RequestParam and #PathVariable annotations, both return 404. Controller is not getting called.
#Controller
#RequestMapping("/home.htm/parameter/surveyId=1")
public class UserHomeController {
#RequestMapping(value = "/home.htm/{surveyId}", method = RequestMethod.GET)
protected String doSubmit(#PathVariable("surveyId") int surveyId, HttpServletRequest request,
HttpServletResponse response, #ModelAttribute("userSurvey") UserSurvey userSurvey, BindingResult result)
throws AdException {
System.out.println("inside home controller, surveyId:"+surveyId");
return null;
}
}
Could anyone please help me out.

How to obtain csrf token in a velocity macro when using spring security

I am trying to create a custom login screen for a spring web security enabled application, and I cannot figure out how to pass the csrf token to velocity (no, I cannot use JSP at the moment).
The model looks something like this:
#RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView login(
#RequestParam(value = "error", required = false) String error,
#RequestParam(value = "logout", required = false) String logout
ModelAndView model = new ModelAndView();
if (error != null) {
model.addObject("error", "Invalid username or password!");
}
if (logout != null) {
model.addObject("msg", "You've been logged out successfully.");
}
model.setViewName("login");
return model;
}
And the relevant section of the velocity template looks like (taken and modified from a jsp example):
<form name='loginForm' action="/login" method='POST'>
<table>
<tr>
<td>User:</td>
<td><input type='text' name='username' value=''></td>
</tr>
<tr>
<td>Password:</td>
<td><input type='password' name='password' /></td>
</tr>
<tr>
<td colspan='2'><input name="submit" type="submit" value="submit" /></td>
</tr>
</table>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
Of course, the ${_csrf.parameterName} and ${_csrf.token} variables are empty, so this only works if I disable csrf protection. So my main question is: how do I fill them in the model (or anywhere else)?
I have found the solution, the main point is that the csrf token is injected into the HttpServletRequest by the CsrfFilter, and you can get the HttpServletRequest object by just adding a HttpServletRequest parameter to your method that handles the request mapping.
So the changes that needed to be done are:
#RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView login(
#RequestParam(value = "error", required = false) String error,
#RequestParam(value = "logout", required = false) String logout,
HttpServletRequest request
){
...
CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrfToken != null) {
model.addObject("_csrf",csrfToken);
}
...
Just to share my little bit, I initially started by using #P.Péter's solution which was fine. but as my application grew to have so many forms, i decided it was too cumbersome using that snippet for every form that I needed to protect from csrf intrusions, so here's what I did so I don't have to repeat across my application.
#ControllerAdvice
public class CsrfControllerAdvice {
#Autowired
private HttpServletRequest request;
#ModelAttribute("_csrf")
public CsrfToken appendCSRFToken(){
//HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return (CsrfToken) request.getAttribute(CsrfToken.class.getName());
}
}
POINT - The idea is to use a #ControllerAdvice which gets called on entering any Spring Controller to attach the CsrfToken to the resulting View using the #ModelAttribute("<attribute-name>") annotation.
NOTE 1 - This _csrf model attribute gets attached for all Views, hence if you want to limit the _csrf processing to selected URLs or Views, see this resource here for very nice samples on how to do that.
NOTE 2 - Notice how I commented out the following line?
//HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
That's because in my case the Autowired HttpServletRequest instance is sufficient for my scenario. However some situations may warrant that you use the commented out instance e.g when you need to obtain the request object from some part of your application that isn't necessarily request scoped... See #Samit G's answer as pointed out in this thread here for more information.

id value does not seem to be captured correctly in servlet

If I arbitrarily assign an 'id' value in the implementing servlet it works; however, if I dont it correctly inherits the id value from the abstract class and skips over the 'if' statement to forward to the url provided in the 'then' statement. What gives? Can tell what is wrong with the 'id' variable:
Abstract servlet snipet:
protected Integer id = null;
private void _doProcess(final HttpServletRequest request, final HttpServletResponse response)
throws IOException, ServletException {
try {
response.setContentType("text/html");
writer = response.getWriter();
final String idString = request.getParameter("id");
if(StringUtil.isNotEmptyStringTrimmed(idString)){
try {
id = Integer.parseInt(idString.trim());
} catch (NumberFormatException e) {
id = null;
}
}
doProcess(request,response);
} finally {
id = null;
}
try {
writer.close();
} catch (Exception e) {
// no-op
}
}
implementing servlet snipet:
public void doProcess(final HttpServletRequest request, final HttpServletResponse response)
throws IOException, ServletException {
// set page title
final HttpSession session = request.getSession();
session.setAttribute("pageTitle", "Training Project 5: Author");
if (id == null){
request.setAttribute("authorNamesList", printAuthorNames());
request.getRequestDispatcher("authorList.jsp").include(request,response);
}else{
final Author author = BEAN_FACTORY.getMember(id);
session.setAttribute("authorId",author.getId());
session.setAttribute("name", author.getName());
session.setAttribute("bio", author.getBio());
session.setAttribute("picture",author.getPicture());
session.setAttribute("bookTitles", printBookTitles(author.getId()));
request.getRequestDispatcher("authorPage.jsp").include(request,response);
}
}
The below jsp code works when the above servlet 'else' code is not in the conditional statement:
<div id="right">
<table class="display" summary="Author Information">
<tr>
<td><span class="brown">Author Id: <c:out value="${authorId}"/></span></td>
</tr>
<tr>
<td><span class="brown">Name: <c:out value="${name}"/></span></td>
</tr>
<tr>
<td><span class="brown">Bio: <c:out value="${bio}"/></span></td>
</tr>
<tr>
<td>
<p>
<span class="brown"><img src="<c:out value="${picture}"/>" alt= ""/></span>
</p>
</td>
</tr>
Unless id is a class field, you're not showing us all the code. It's also unclear what you mean by 'it doesn't work.' The expected content is empty? You get an exception?
'Id' will always be null or undefined rather if its not initialized.
Figured out what the issue was. . . sorry guys (and gals) I didn't give everyone enough info to solve the problem. There is a authorList.jsp page that prints the list that everyone chooses from (see below). The user chooses the author they want to see information about in the authorList.jsp page, then the author id parameter is forwarded back to the servlet which forwards the user to the authorPage.jsp which displays the individual author information.
Basically, I had used the exact .jsp name of the following jsp page (authorPage.jsp) as opposed to the servlet mapping designation on the web.xml (listAuthor). I had:
<table summary="Author List">
<c:forEach items="${authorNamesList}" var="name">
<tr>
<td><span class="brown">${name.value}</span></td>
</tr>
</c:forEach>
Should have been:
<table summary="Author List">
<c:forEach items="${authorNamesList}" var="name">
<tr>
<td><span class="brown">${name.value}</span></td>
</tr>
</c:forEach>
</table>
Some times it's better to just leave and come back later to look at it fresh! Happy Thanksgiving everybody:)

Spring MVC - Child entity lost after submit

I'm going to try to explain my problem as completely and shortly as I can...
A web application, made on Spring MVC 2.5 + Hibernate + Java 6 (not using annotation!).
I've got a controller extending SimpleFormController and a jsp page that is its formView and successView.
This controller should help me to insert into db an entity PracticeT that has connected (many to one) a lookup entity PracticeConfT (think about it as a "typology"). I need to choose that "typology" through a drop-down menu. In my webapp I need to be able to save data inserted and when I want, to submit the request for approval.
The page has some text fields and that drop-down menu. The bean called as default "command" is NewPracticeBean that has within a reference to an object PracticeT.
THE PROBLEM IS: I fill the form, I select a typology from the drop-down menu, I submit form and save data on DB but when I come back to the view, every property is there but the drop-down menu it is not: it has all the options allowed but no one selected. Some checks revealed that the entity PracticeConfT is null (but it has been recorded on db correctly and debugging it is still there in the model until the very end of the method onSubmit!!!).
I hope someone can help me. Thank you in advance!
Bye,
Dolfiz
Here some useful code:
(I don't think that hibernate config can be the problem, but if you need it, I can post it too)
newPractice.jsp
<form:form id="newPracticeForm" commandName="command">
<input type="hidden" name="action"/>
<spring:nestedPath path="practiceT">
<table class="table-data-form">
<tr>
<td class="left"><spring:message code="" text="Practice type" /></td>
<td>
<form:select path="practiceConfT" multiple="false">
<form:option value="" label="- seleziona -"/>
<form:options items="${practiceTypeList}" itemValue="idPracticeConf" itemLabel="practiceName"/>
</form:select>
</td>
</tr>
<tr>
<td class="left">
<spring:message code="" text="Opzione divisa" />
<br/><form:errors cssClass="errors" path="opzioneDivisa" />
</td>
<td><form:input path="opzioneDivisa" /></td>
</tr>
<tr>
<td colspan="1">
<input type="submit" name="submit" id="submit" value="Save" class="buttonEMS" style="width:100px;" />
</td>
</tr>
</table>
</spring:nestedPath>
</form:form>
NewPracticeBean.java
public class NewPracticeBean implements Serializable{
private PracticeT practiceT;
private String action;
private boolean typeSelected;
public NewPracticeBean(){
super();
this.practiceT = new PracticeT();
}
// getters & setters...
}
PracticeT.java
public class PracticeT implements java.io.Serializable {
private long idPractice;
private PracticeConfT practiceConfT;
private String opzioneDivisa;
// getters & setters...
}
PracticeConfT.java
public class PracticeConfT implements java.io.Serializable {
public static final String PRACTICE_NAME = "practiceName";
private long idPracticeConf;
private String practiceName;
// getters & setters...
}
NewPracticeController.java
public class NewPracticeController extends SimpleFormController{
protected SmartLogger log = SmartLogger.getLogger(this.getClass());
private PracticeSu practiceSu;
private ConfigurationSu configurationSu;
private HibernateEntityDataBinder practiceConfTBinder;
private HibernateEntityDataBinder practiceTBinder;
public NewPracticeController() {
setCommandClass(NewPracticeBean.class);
setCommandName("command");
}
#Override
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
log.trace("NewPracticeController -- initBinder");
super.initBinder(request, binder);
binder.registerCustomEditor(PracticeT.class, "practiceT", practiceTBinder);
binder.registerCustomEditor(PracticeConfT.class, "practiceT.practiceConfT", practiceConfTBinder);
}
#Override
protected Map referenceData(HttpServletRequest request) throws Exception {
log.trace("NewPracticeController -- referenceData");
Map model = new HashMap();
RetrieveAllEntitiesReq req = new RetrieveAllEntitiesReq();
req.setEntity(PracticeConfT.class);
req.setOrderProperty(PracticeConfT.PRACTICE_NAME);
RetrieveAllEntitiesResp resp = configurationSu.retrieveAllEntities(req);
List entitiesList = resp.getEntitiesList();
model.put("practiceTypeList", entitiesList);
return model;
}
#Override
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception {
NewPracticeBean practiceBean = (NewPracticeBean)command;
Map model = errors.getModel();
CreateNewPracticeReq req = new CreateNewPracticeReq();
req.setPracticeT(practiceBean.getPracticeT());
CreateNewPracticeResp resp = practiceSu.createNewPractice(req);
practiceBean.setPracticeT(resp.getPracticeT());
model.putAll(referenceData(null));
model.put(getCommandName(), practiceBean);
return new ModelAndView(getSuccessView(), model);
}
// setters and getters...
}
After spending some time with OptionsTag, OptionWriter and SelectValueComparator, I would say, then output of "selected" is based on Object.equals.
So if for any reason (Lazyloading...) the Object PracticeT.practiceConfT and the according Objects of model.put("practiceTypeList", entitiesList) are not the SAME (==) then forms:options will not select them as long as the equals method is not correct implemented.
So I guess you need to implement a correct equals method, even if this did not fix this problem, it is always better to have a correct equals method than a wrong or none.
Correct implemented means that it must pay attention to the fact that is used with Hibernate. (for example use if (Hibernate.getClass(this) != Hibernate.getClass(other)) instead of `if (this.getClass() != other.getClass() )

Categories