I have a pretty simple user settings form:
<form:form method="post" id="fm1" cssClass="fm-v clearfix" commandName="${commandName}" htmlEscape="true">
<div class="row fl-controls-left">
<spring:message code="screen.userSettings.label.timeZone.accesskey" var="timeZoneAccessKey" />
<label for="timeZone" class="fl-label"><spring:message code="screen.userSettings.label.timeZone" /></label>
<form:select id="timeZone" path="timeZone" accesskey="${timeZoneAccessKey}">
<form:options items="${user.supportedTimeZones}" itemLabel="label" itemValue="id" />
</form:select>
<c:forEach items="${user.answers}" var="answer" varStatus="loop">
<div class="row fl-controls-left">
<form:select path="answers[${loop.index}].questionId">
<option value="-1"><spring:message code="screen.userSettings.question.selectOne" /></option>
<form:options items="${user.supportedQuestions}" itemLabel="question" itemValue="id" />
</form:select>
<form:input path="answers[${loop.index}].answer" size="30" autocomplete="false" htmlEscape="true" type="text" cssClass="required" cssErrorClass="error"/>
</div>
</c:forEach>
</div>
</form:form>
Basically, you can set your timezone, and provide answers to a list forgotten password questions. The model object bound to this form is basically this:
public class User implements Serializable {
private static final long serialVersionUID = 8974875234954842283L;
private List<Answer> answers;
private List<Question> supportedQuestions;
private List<TimeZone> supportedTimeZones;
private String timeZone;
public User() {
credentials = new UsernamePasswordCredentials();
}
public List<Answer> getAnswers() {
return answers;
}
public List<Question> getSupportedQuestions() {
return supportedQuestions;
}
public List<TimeZone> getSupportedTimeZones() {
return supportedTimeZones;
}
public String getTimeZone() {
return timeZone;
}
public void setAnswers( List<Answer> answers ) {
this.answers = answers;
}
public void setSupportedQuestions( List<Question> supportedQuestions ) {
this.supportedQuestions = supportedQuestions;
}
public void setSupportedTimeZones( List<TimeZone> supportedTimeZones ) {
this.supportedTimeZones = supportedTimeZones;
}
public void setTimeZone( String timeZone ) {
this.timeZone = timeZone;
}
...
}
The form displays just fine. It provides a single drop down showing all supported timezones followed by few rows of question/answer pairs with the questions being drop downs containing all of the supported questions. When I submit the form back, processing fails because the List<Answer> answers
is not getting updated with the values from the form. My question is two fold, first, what did I do wrong? Second, how should I approach debugging this type of mapping problem in the future?
I have tried stepping through the code with the debugger. The setTimeZone()
method is getting called with the value from the form, but the setAnswers()
method is not. Nor are the setQuestionId()
or setAnswer()
methods of the Answer
class. I looked at the Request
object and it has keys for all the answer[x].questionId
and answer[x].answer
parameters, but not sure what the values are. What would you recommend doing to track down this type of issue?
-------------------------- UPDATE --------------------------------
I just pulled this from the debugger:
map[
'answers[4].questionId' -> 'cn=honeymoon,ou=questions,dc=company'
'answers[2].questionId' -> 'cn=honeymoon,ou=questions,dc=company'
'answers[0].questionId' -> 'cn=firstPet,ou=questions,dc=company'
'lt' -> 'LT-786e9b77-efbd-f302-062e-90364cc4634aZe1s2'
'answers[1].answer' -> 'qwer'
'answers[3].answer' -> 'poiu'
'submit' -> 'Save'
'answers[3].questionId' -> 'cn=honeymoon,ou=questions,dc=company'
'answers[1].questionId' -> 'cn=mothersMaiden,ou=questions,dc=company'
'answers[2].answer' -> 'zxcv'
'answers[4].answer' -> 'lkjh'
'timeZone' -> 'America/New_York'
'_eventId' -> 'submit'
'answers[0].answer' -> 'asdf'
]
It is what is contained in the requestParameterMap. Clearly it shows that values were returned for answers
(both questionId
and answer
). Why is spring not calling setAnswers()
on the model object (remember that setTimeZone()
is getting called)?