Announcement Announcement Module
Collapse
No announcement yet.
Strange Validation Problem Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Strange Validation Problem

    Hey, everyone. I'm new to Spring MVC but I've been working with it for several weeks now. I went through the "Spring MVC Step-By-Step" tutorial (http://static.springsource.org/docs/...-step-by-step/) and followed everything relatively well.

    I worked the tutorial from front to back, but I took a different approach on it. Instead of configuring things exactly like in the tutorial, I used Spring 3 and annotations to develop my applications. It worked perfectly (including validation)! I was very please with how easy an environment it was to work in.

    Since I got everything (including validation) to work with annotations in the tutorial, I moved on to bigger-and-better things and creating my own application. When I created my own application, using virtually identical configuration and convention as the way I did the tutorial, everything worked except validation!

    So, I figured I'd give y'all some sample code of how I did things, hoping you'd be able to see whatever it is I'm missing that's making this not work.

    Here are various code snippets from the TUTORIAL, which worked perfectly:

    web.xml
    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.4"
             xmlns="http://java.sun.com/xml/ns/j2ee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
             http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    	<servlet>
    		<servlet-name>springapp</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<load-on-startup>1</load-on-startup>
    	</servlet>
    	<servlet-mapping>
    		<servlet-name>springapp</servlet-name>
    		<url-pattern>*.htm</url-pattern>
    	</servlet-mapping>
    	<welcome-file-list>
    		<welcome-file>index.jsp</welcome-file>
    	</welcome-file-list>
    	<jsp-config>
    		<taglib>
    			<taglib-uri>/spring</taglib-uri>
    			<taglib-location>/WEB-INF/tld/spring-form.tld</taglib-location>
    		</taglib>
    	</jsp-config>
    </web-app>
    springapp-servlet.xml
    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
    
    	<context:component-scan base-package="springapp"/>
    	<context:annotation-config/>
    	<tx:annotation-driven/>
    
    	<bean id="validator" class="org.springmodules.validation.bean.BeanValidator">
    		<property name="configurationLoader">
    			<bean class="org.springmodules.validation.bean.conf.loader.annotation.AnnotationBeanValidationConfigurationLoader"/>
    		</property>
    	</bean>
    	<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    		<property name="webBindingInitializer">
    			<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
    				<property name="validator" ref="validator"/>
    			</bean>
    		</property>
    	</bean>
    	<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    		<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    		<property name="prefix" value="/WEB-INF/jsp/"/>
    		<property name="suffix" value=".jsp"/>
    	</bean>
    
    	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    		<qualifier value="main"/>
    		<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
    		<property name="url" value="jdbc:sqlserver://hostname:1433;databasename=springapp"/>
    		<property name="username" value="springapp"/>
    		<property name="password" value="password"/>
    	</bean>
    
    	<bean class="springapp.service.SimpleProductManager">
    		<qualifier value="main"/>
    	</bean>
    
    	<bean class="springapp.repository.ProductDaoJdbc">
    		<qualifier value="main"/>
    	</bean>
    </beans>
    PriceIncrease.java
    Code:
    import org.springmodules.validation.bean.conf.loader.annotation.handler.*;
    ...
    public class PriceIncrease
    {
    	@NotNull(message="The percentage cannot be blank!")
    	@Max(value=50,message="The percentage cannot be greater than 50!")
    	@Min(value=1,message="The percentage cannot be less than 1!")
    	private int percentage;
    ...
    jsp/priceincrease.jsp
    Code:
    ...
    <form:form method="post" modelAttribute="priceIncrease">
    	<table width="95%" bgcolor="#F8F8FF" border="0" cellspacing="0" cellpadding="5">
    		<tr>
    			<td align="right" width="20%">Increase (%):</td>
    			<td width="20%">
    				<form:input path="percentage"/>
    			</td>
    			<td width="60%">
    				<form:errors path="percentage" cssClass="error"/>
    			</td>
    		</tr>
    	</table>
    	<br/>
    	<input type="submit" align="center" value="Execute">
    </form:form>
    ...
    InventoryController.java
    Code:
    @Controller
    @RequestMapping("/inventory/*")
    public class InventoryController
    {
    ...
    	@RequestMapping(value="priceIncrease.htm", method=RequestMethod.GET)
    	public String priceIncrease(Map<String, Object> model)
    		throws ServletException, IOException
    	{
    		logger.info("New getting form");
    		model.put("priceIncrease", new PriceIncrease());
    		return "priceincrease";
    	}
    
    	@RequestMapping(value="priceIncrease.htm", method=RequestMethod.POST)
    	public ModelAndView priceIncrease(@Valid PriceIncrease increaser, BindingResult binder, Map<String, Object> model)
    		throws ServletException, IOException
    	{
    		this.logger.info("Form submitted to increase prices.");
    
    		if(binder.hasErrors())
    		{
    			this.logger.warn("Form had errors in it: " +  binder.getAllErrors().get(0).getCode() + " (" + binder.getAllErrors().get(0).getDefaultMessage() + ")...");
    			model.put("priceIncrease", increaser);
    			return new ModelAndView("priceincrease");
    		}
    
    		int increase = increaser.getPercentage();
    		logger.info("Increasing prices by " + increase + "%.");
    
    		productManager.increasePrice(increase);
    
    		logger.info("Returning from priceincrease view.");
    
    		return new ModelAndView(new RedirectView("index.htm"));
    	}
    ...
    With this code, when I put in the incorrect price increase (< 1 or > 50), a warning is saved to the log and the HTML output around the errors tag is:
    Code:
    <td width="60%">
    	<span id="percentage.errors" class="error">The percentage cannot be less than 1!</span>
    </td>
    Like I said, it works perfectly. I'm limited to 10000 characters, so I'll reply with the code snippets from my real-life application, which is the one I'm having problems with.

  • #2
    Sorry, had to wait for my thread to be approved.

    Now, here are the same snippets of code from my real-life application, which, from what I can tell, is pretty much written the same way and so should work the same way:

    web.xml
    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.4"
             xmlns="http://java.sun.com/xml/ns/j2ee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
             http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    	<servlet>
    		<servlet-name>migrationapp</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<load-on-startup>1</load-on-startup>
    	</servlet>
    	<servlet-mapping>
    		<servlet-name>migrationapp</servlet-name>
    		<url-pattern>/do/*</url-pattern>
    	</servlet-mapping>
    	<welcome-file-list>
    		<welcome-file>index.jsp</welcome-file>
    	</welcome-file-list>
    	<jsp-config>
    		<taglib>
    			<taglib-uri>/spring</taglib-uri>
    			<taglib-location>/WEB-INF/tld/spring-form.tld</taglib-location>
    		</taglib>
    	</jsp-config>
    </web-app>
    migrationapp-servlet.xml
    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
    
    	<context:component-scan base-package="[my.base.package]"/>
    	<context:annotation-config/>
    	<tx:annotation-driven/>
    
    	<bean id="validator" class="org.springmodules.validation.bean.BeanValidator">
    		<property name="configurationLoader">
    			<bean class="org.springmodules.validation.bean.conf.loader.annotation.AnnotationBeanValidationConfigurationLoader"/>
    		</property>
    	</bean>
    	<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    		<property name="webBindingInitializer">
    			<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
    				<property name="validator" ref="validator"/>
    			</bean>
    		</property>
    	</bean>
    	<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    		<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    		<property name="prefix" value="/WEB-INF/views/"/>
    		<property name="suffix" value=".jsp"/>
    	</bean>
    
    	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    		<qualifier value="main"/>
    		<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
    		<property name="url" value="jdbc:sqlserver://hostname:1433;databasename=migrationApp"/>
    		<property name="username" value="migrationApp"/>
    		<property name="password" value="password"/>
    	</bean>
    </beans>
    UserAccountForm.java
    Code:
    import org.springmodules.validation.bean.conf.loader.annotation.handler.*;
    ...
    public class UserAccountForm
    {
    	@NotNull(message="The percentage cannot be blank!")
    	@Max(value=50,message="The percentage cannot be greater than 50!")
    	@Min(value=1,message="The percentage cannot be less than 1!")
    	private int percentage;
    
    @NotBlank(message="The username is required!")
    	@MaxLength(message="The username is too long!", value=100)
    	private String username;
    
    	@NotBlank(message="The email address is required!")
    	@MaxLength(message="The email address is too long!", value=100)
    	@Email(message="The email address provided is not a valid email address!")
    	private String email;
    
    	@NotBlank(message="The password is required!")
    	@MinLength(message="The password must be at least 6 characters!", value=6)
    	@MaxLength(message="The password must not be more than 20 characters!", value=20)
    	private String password;
    
    	@NotBlank(message="You must confirm the password!")
    	@Expression(value="passwordConfirm EQUALS password", message="The passwords do not match!")
    	private String passwordConfirm;
    
    	private boolean administrator = false;
    ...
    views/users/new.jsp
    Code:
    ...
    <form:form method="post" modelAttribute="newAccountForm">
    	<table width="100%" bgcolor="#FFFFFF" border="0" cellspacing="2" cellpadding="0">
    		<tr>
    			<td align="right" width="20%">Increase (%):</td>
    			<td width="20%">
    				<form:input path="percentage"/>
    			</td>
    			<td width="60%">
    				<form:errors path="percentage" cssClass="error"/>
    			</td>
    		</tr>
    		<tr>
    			<td>Username</td>
    			<td>
    				<form:input path="username"/>
    			</td>
    			<td>
    				<form:errors path="username" cssClass="error"/>
    			</td>
    		</tr>
    		<tr>
    			<td>Email Address</td>
    			<td>
    				<form:input path="email"/>
    			</td>
    			<td>
    				<form:errors path="email" cssClass="error"/>
    			</td>
    		</tr>
    		<tr>
    			<td>Password</td>
    			<td>
    				<form:password path="password"/>
    			</td>
    			<td>
    				<form:errors path="password" cssClass="error"/>
    			</td>
    		</tr>
    		<tr>
    			<td>Confirm Password</td>
    			<td>
    				<form:password path="passwordConfirm"/>
    			</td>
    			<td>
    				<form:errors path="passwordConfirm" cssClass="error"/>
    			</td>
    		</tr>
    		<tr>
    			<td colspan="3">
    				<form:checkbox path="administrator"/> This user is an administrator
    			</td>
    		</tr>
    	</table><br/>
    	<br/>
    	<input type="submit" value="Add User"/>
    </form:form>
    ...
    UserAccountController.jsp
    Code:
    @Controller
    @RequestMapping("account")
    public class UserAccountController
    {
    ...
    	@RequestMapping(value="new", method=RequestMethod.GET)
    	public String addAccount(Map<String, Object> model)
    	{
    		this.logger.info("Generating the form to add an account.");
    		model.put("title", this.title + " :: Add Account");
    		model.put("newAccountForm", new UserAccountForm());
    		return "users/new";
    	}
    	
    	@RequestMapping(value="new", method=RequestMethod.POST)
    	public ModelAndView addAccount(@Valid UserAccountForm form, BindingResult binder, Map<String, Object> model)
    	{
    		this.logger.info("Form submitted to add an account.");
    		
    		model.put("title", this.title + " :: Add Account");
    
    		if(binder.hasErrors())
    		{
    			this.logger.warn("Form had errors in it: " +  binder.getAllErrors().get(0).getCode() + " (" + binder.getAllErrors().get(0).getDefaultMessage() + ")...");
    			model.put("newAccountForm", form);
    			return new ModelAndView("users/new");
    		}
    		...
    	}
    Again, this works almost identically, including that when any of the fields are invalid a warning gets saved to the log, however, the HTML output around the errors tag is

    Code:
    <td></td>
    Which isn't very helpful at all. I have hammered at this one problem for days. Everything else is working perfectly - I can CRUD against the database on both projects, if I fail validation I get sent back to the form and a warning gets logged ... it's just that on the tutorial application the validation annotation "message" is displayed, and on the real-life application the span isn't even printed out, much less with the message.

    There are no warnings, errors or exceptions in the log file other than those I have intentionally logged to it when validation rules are violated. No informational messages indicate why the form:errors tags aren't displaying the errors.

    I do not have a messages.properties file (yet) on EITHER application. I'm relying solely on the annotation default message for now. I have tried adding a message.properties file to the WEB-INF/classes directory (and the appropriate configurations) but that didn't help any, and since it works without it in the tutorial, it should work without it in the real-life application.

    I have all the same JARs in the projects. I can provide a list of them if need be.

    Any ideas? Thanks in advance for any help.

    Comment


    • #3
      Anyone else able to duplicate this? I'm not sure what else to try...

      Comment


      • #4
        Problem Solved ... Is This a Bug?

        I solved the problem, and I think I'll file a bug report, because this seems to be a problem.

        My method code (abridged) in the one that wasn't working was as follows:

        Code:
        	@RequestMapping(value="new", method=RequestMethod.GET)
        	public String addAccount(Map<String, Object> model)
        	{
        		this.logger.info("Generating the form to add an account.");
        		model.put("title", this.title + " :: Add Account");
        		model.put("newAccountForm", new UserAccountForm());
        		return "users/new";
        	}
        
        	@RequestMapping(value="new", method=RequestMethod.POST)
        	public ModelAndView addAccount(@Valid UserAccountForm userAccountForm, BindingResult binder, Map<String, Object> model)
        	{
        		this.logger.info("Form submitted to add an account.");
        		
        		model.put("title", this.title + " :: Add Account");
        
        		if(binder.hasErrors())
        		{
        			this.logger.warn("Form had errors in it: " +  binder.getAllErrors().get(0).getCode() + " (" + binder.getAllErrors().get(0).getDefaultMessage() + ")...");
        			model.put("newAccountForm", userAccountForm);
        			return new ModelAndView("users/new");
        		}
        		...
        	}
        I changed it to this:

        Code:
        	@RequestMapping(value="new", method=RequestMethod.GET)
        	public String addAccount(Map<String, Object> model)
        	{
        		this.logger.info("Generating the form to add an account.");
        		model.put("title", this.title + " :: Add Account");
        		model.put("userAccountForm", new UserAccountForm());
        		return "users/new";
        	}
        
        	@RequestMapping(value="new", method=RequestMethod.POST)
        	public ModelAndView addAccount(@Valid UserAccountForm userAccountForm, BindingResult binder, Map<String, Object> model)
        	{
        		this.logger.info("Form submitted to add an account.");
        		
        		model.put("title", this.title + " :: Add Account");
        
        		if(binder.hasErrors())
        		{
        			this.logger.warn("Form had errors in it: " +  binder.getAllErrors().get(0).getCode() + " (" + binder.getAllErrors().get(0).getDefaultMessage() + ")...");
        			model.put("userAccountForm", userAccountForm);
        			return new ModelAndView("users/new");
        		}
        		...
        	}
        Note that the only change was changing model.put("newAccountForm", ...) to model.put("userAccountForm", ...).

        And it works now. Apparently, if you don't use the "non-qualified command class name in property notation" as the name of the command object model attribute, the command object and its validation will still work but DISPLAYING the validation errors will not! Ummm?

        Anyway, I'm really glad my problem is finally solved (by accident, actually), but does anyone have any thoughts on this? Does it seem as much like a bug to you as it does to me? Should I file a bug report?

        Comment


        • #5
          Hi beamerblvd

          what jar are you using for the following classes. Thanks in advance.

          org.springmodules.validation.bean.BeanValidator
          org.springmodules.validation.bean.conf.loader.anno tation.AnnotationBeanValidationConfigurationLoader

          Comment


          • #6
            I'm using spring-modules-validation.jar from spring-modules-0.9.zip. I don't have any other Spring Modules jars in my path but, again, no errors or related info/debug messages were ever appearing in the log other than the warnings I intentionally put in there to confirm that errors were being added to the BindingResult object, so I don't think it was missing any dependencies.

            Comment

            Working...
            X