1 2 Previous Next 21 Replies Latest reply on Oct 15, 2007 3:31 AM by Olivier Thierry

    How to write a validator that compares two fields

    Olivier Thierry Apprentice

      Hi,

      I need to write a JSF validator which compares two fields. It could be used for example to compare "new password" and "confirm password" fields.

      To do this, I wrote the following class :

      @Name (value="validator.compareFields")
      @org.jboss.seam.annotations.faces.Validator
      
      public class CompareFieldsValidator implements Validator {
      
       private @In FacesMessages facesMessages;
      
       public void validate(FacesContext context, UIComponent cmp, Object value)
       throws ValidatorException
       {
       String compareTo = (String) cmp.getAttributes().get("compareTo");
       UIInput input = (UIInput) cmp.findComponent(compareTo);
       String otherValue = (String) input.getSubmittedValue();
       boolean error = false;
       if (value != null) {
       if (! value.equals(otherValue)) error = true;
       }
       else {
       if (otherValue != null) error = true;
       }
       if (error) {
       facesMessages.addToControlFromResourceBundle(cmp.getId(), "error.fieldsNotEqual", null);
       throw new ValidatorException(new FacesMessage());
       }
       }
      }


      Then in my JSF, I use it this way :

      <h:outputLabel value="New password :" for="newPassword" />
      <h:inputSecret id="newPassword" value="#{userCrud.newPassword}">
       <f:validator validatorId="validator.compareFields" />
       <f:attribute name="compareTo" value="confirmPassword" />
      </h:inputSecret>
      
      <h:outputLabel value="Confirm password :" for="confirmPassword" />
      <h:inputSecret id="confirmPassword" value="#{userCrud.confirmPassword}" />


      It works well except when my inputSecret fields are surrounded by <s:decorate>.

      <s:decorate template="/decorateField.xhtml">
       <h:inputSecret id="newPassword" value="#{userCrud.newPassword}">
       <f:validator validatorId="validator.compareFields" />
       <f:attribute name="compareTo" value="confirmPassword" />
       </h:inputSecret>
      </s:decorate>


      In this case, the UIComponent.findComponent() can't find the field corresponding to "compareTo" attribute. For what I understood, it works only if the two fields to compare have the same parent in JSF components tree (while I thought findComponent was searching recursively the component). Anyone knows how I could make it work ?

      Note also that I'm not sure the way I used facesMessages object, especially the way I throw ValidatorException. I couldn't find an example with message in a resource bundle. So please tell me if there's a better way to write it.

      Thanks in advance

        • 1. Re: How to write a validator that compares two fields
          Olivier Thierry Apprentice

          I finally wrote my own findComponent method, which looks for recursively a component.

          The method is like this :

          public class JSFUtils {
          
           public static UIComponent findComponent(UIComponent component, String id) {
           if (component.getId().equals(id)) {
           return component;
           }
           else {
           List<UIComponent> children = component.getChildren();
           for (UIComponent myComponent : children) {
           UIComponent componentFound = findComponent(myComponent, id);
           if (componentFound != null) return componentFound;
           }
           return null;
           }
           }
          
          }


          Then my validator class is like this :
          public void validate(FacesContext context, UIComponent cmp, Object value)
           throws ValidatorException {
           String compareTo = (String) cmp.getAttributes().get("compareTo");
           String messageKey = (String) cmp.getAttributes().get("message");
           UIInput input = (UIInput) JSFUtils.findComponent(context.getViewRoot(), compareTo);
           String otherValue = (String) input.getValue();
           boolean error = false;
           if (value != null) {
           if (! value.equals(otherValue)) {
           error = true;
           }
           }
           else {
           if (otherValue != null) {
           error = true;
           }
           }
           if (error) {
           String msg = Messages.instance().get(messageKey);
           throw new ValidatorException(new FacesMessage(msg));
           }
          }


          Hope it will help people who need this.

          • 2. Re: How to write a validator that compares two fields
            Olivier Thierry Apprentice

            One more problem I can't find how to workaround ...

            I try to use my validator within a facelet taglib. I would like it to be used this way (t4:compareFields tag) :

            <s:decorate template="/decorateField.xhtml" for="confirmerMotDePasse">
             <h:inputSecret id="confirmerMotDePasse" value="#{employeCrud.confirmerMotDePasse}" required="#{employe.id == null}">
             <t4:compareFields compareTo="nouveauMotDePasse" message="error.motDePasse.differents" />
             </h:inputSecret>
            </s:decorate>


            My validator class has two properties : "compareTo" and "message", with getters and setters. But these properties are always null when the validate() method is called.

            I added a t4.taglib.xml file in /WEB-INF :
            <?xml version="1.0" encoding="UTF-8"?>
            <!DOCTYPE facelet-taglib PUBLIC
             "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
             "http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
            <facelet-taglib xmlns="http://java.sun.com/JSF/Facelet">
             <namespace>http://myCompany.fr/t4/taglib</namespace>
             <tag>
             <tag-name>compareFields</tag-name>
             <validator>
             <validator-id>validator.compareFields</validator-id>
             </validator>
             </tag>
            </facelet-taglib>


            Note I had to remove @Name and @Validator annotations in my validator class, because it could not find the validator-id (is it a bug ?). So I added these lines in faces-config.xml file :

            <validator>
             <validator-id>validator.compareFields</validator-id>
             <validator-class>fr.myCompany.t4.gui.validators.CompareFieldsValidator</validator-class>
            </validator>


            Is there something special to do in the validator class so that facelets can use my validator's setters ?

            Thanks in advance

            • 3. Re: How to write a validator that compares two fields
              Olivier Thierry Apprentice

              I found my error : my validator class just had to implement javax.faces.component.StateHolder interface, then it works.

              Anyway, I think there is a bug with @Validator annotation. Or maybe I misunderstood what this annotation is used for. I though this annotation was used to prevent from declaring the validator in faces-config.xml. And it works well indeed when you use <f:validator> in a facelet. But if you refer to the validator in a *.taglib.xml file, it can't find the validator. What do you think about this ?

              • 4. Re: How to write a validator that compares two fields
                Nicklas Karlsson Master

                Haven't read all your code but if you want to compare two fields, how about:

                @AssertTrue
                public void passwordsEqual() {
                return password.equals(comparePassword);
                }

                • 5. Re: How to write a validator that compares two fields
                  Olivier Thierry Apprentice

                  I could have done like this, but I wanted to validate in my facelet, i.e. a taglib.

                  • 6. Re: How to write a validator that compares two fields
                    Pete Muir Master

                    It should be fine using it in your taglib.xml - I've not used it for validators but it works correctly for converters. Post the exception you get

                    • 7. Re: How to write a validator that compares two fields
                      Olivier Thierry Apprentice

                      Thanks Pete. The exception I get :

                      14:00:08,002 WARN [lifecycle] executePhase(RENDER_RESPONSE 6,com.sun.faces.context.FacesContextImpl@26b13c) threw exception
                      javax.faces.FacesException: Erreur dans lexpression: Object nommé validator.equals ne peut tre trouvé.
                       at com.sun.faces.application.ApplicationImpl.createValidator(ApplicationImpl.java:900)
                       at org.jboss.seam.jsf.SeamApplication.createValidator(SeamApplication.java:140)
                       at com.sun.facelets.tag.jsf.ValidateHandler.createValidator(ValidateHandler.java:116)
                       at com.sun.facelets.tag.jsf.ValidateHandler.apply(ValidateHandler.java:90)
                       at com.sun.facelets.tag.jsf.ComponentHandler.applyNextHandler(ComponentHandler.java:314)
                       at com.sun.facelets.tag.jsf.ComponentHandler.apply(ComponentHandler.java:169)
                       at com.sun.facelets.tag.ui.DecorateHandler.apply(DecorateHandler.java:122)
                       at com.sun.facelets.impl.DefaultFaceletContext$TemplateManager.apply(DefaultFaceletContext.java:312)
                       at com.sun.facelets.impl.DefaultFaceletContext.includeDefinition(DefaultFaceletContext.java:282)
                       at com.sun.facelets.tag.ui.InsertHandler.apply(InsertHandler.java:68)
                       at com.sun.facelets.tag.jsf.ComponentHandler.applyNextHandler(ComponentHandler.java:314)
                       at com.sun.facelets.tag.jsf.ComponentHandler.apply(ComponentHandler.java:169)
                       at com.sun.facelets.tag.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:47)
                       at com.sun.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:119)
                       at com.sun.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:49)
                       at com.sun.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:25)
                       at com.sun.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:248)
                       at com.sun.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:294)
                       at com.sun.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:273)
                       at com.sun.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:144)
                       at com.sun.facelets.tag.ui.DecorateHandler.apply(DecorateHandler.java:105)
                       at org.jboss.seam.ui.handler.DecorateHandler.applyNextHandler(DecorateHandler.java:32)
                       at com.sun.facelets.tag.jsf.ComponentHandler.apply(ComponentHandler.java:169)
                       at com.sun.facelets.tag.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:47)
                       at com.sun.facelets.tag.jsf.ComponentHandler.applyNextHandler(ComponentHandler.java:314)
                       at com.sun.facelets.tag.jsf.ComponentHandler.apply(ComponentHandler.java:169)
                       at com.sun.facelets.tag.jsf.ComponentHandler.applyNextHandler(ComponentHandler.java:314)
                       at com.sun.facelets.tag.jsf.ComponentHandler.apply(ComponentHandler.java:169)
                       at com.sun.facelets.tag.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:47)
                       at com.sun.facelets.tag.jsf.ComponentHandler.applyNextHandler(ComponentHandler.java:314)
                       at com.sun.facelets.tag.jsf.ComponentHandler.apply(ComponentHandler.java:169)
                       at com.sun.facelets.tag.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:47)
                       at com.sun.facelets.tag.jsf.ComponentHandler.applyNextHandler(ComponentHandler.java:314)
                       at com.sun.facelets.tag.jsf.ComponentHandler.apply(ComponentHandler.java:169)
                       at com.sun.facelets.tag.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:47)
                       at com.sun.facelets.tag.jsf.ComponentHandler.applyNextHandler(ComponentHandler.java:314)
                       at com.sun.facelets.tag.jsf.ComponentHandler.apply(ComponentHandler.java:169)
                       at com.sun.facelets.tag.jsf.core.ViewHandler.apply(ViewHandler.java:109)
                       at com.sun.facelets.tag.ui.DefineHandler.applyDefinition(DefineHandler.java:64)
                       at com.sun.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:128)
                       at com.sun.facelets.impl.DefaultFaceletContext$TemplateManager.apply(DefaultFaceletContext.java:312)
                       at com.sun.facelets.impl.DefaultFaceletContext.includeDefinition(DefaultFaceletContext.java:282)
                       at com.sun.facelets.tag.ui.InsertHandler.apply(InsertHandler.java:68)
                       at com.sun.facelets.tag.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:47)
                       at com.sun.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:49)
                       at com.sun.facelets.tag.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:47)
                       at com.sun.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:25)
                       at com.sun.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:248)
                       at com.sun.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:294)
                       at com.sun.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:273)
                       at com.sun.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:144)
                       at com.sun.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:113)
                       at com.sun.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:49)
                       at com.sun.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:25)
                       at com.sun.facelets.impl.DefaultFacelet.apply(DefaultFacelet.java:95)
                       at com.sun.facelets.FaceletViewHandler.buildView(FaceletViewHandler.java:509)
                       at com.sun.facelets.FaceletViewHandler.renderView(FaceletViewHandler.java:552)
                       at org.ajax4jsf.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:108)
                       at org.ajax4jsf.application.AjaxViewHandler.renderView(AjaxViewHandler.java:216)
                       at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:106)
                       at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:251)
                       at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:144)
                       at javax.faces.webapp.FacesServlet.service(FacesServlet.java:245)
                       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
                       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                       at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:307)
                       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:83)
                       at org.jboss.seam.debug.hot.HotDeployFilter.doFilter(HotDeployFilter.java:68)
                       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                       at org.jboss.seam.web.MultipartFilter.doFilter(MultipartFilter.java:85)
                       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                       at org.jboss.seam.web.ExceptionFilter.doFilter(ExceptionFilter.java:64)
                       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                       at org.jboss.seam.web.RedirectFilter.doFilter(RedirectFilter.java:44)
                       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                       at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:141)
                       at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:281)
                       at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:60)
                       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                       at org.jboss.seam.web.LoggingFilter.doFilter(LoggingFilter.java:58)
                       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                       at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:150)
                       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                       at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
                       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                       at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
                       at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
                       at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:179)
                       at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:433)
                       at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
                       at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
                       at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:104)
                       at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:156)
                       at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
                       at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:241)
                       at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
                       at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:580)
                       at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
                       at java.lang.Thread.run(Thread.java:595)
                      


                      • 8. Re: How to write a validator that compares two fields
                        Pete Muir Master

                        And do you see the validator component being started when Seam starts?

                        • 10. Re: How to write a validator that compares two fields
                          Pete Muir Master

                          So there's the problem - make sure you have a seam.properties in the root of that archive.

                          • 11. Re: How to write a validator that compares two fields
                            Olivier Thierry Apprentice

                            Well ... It looks like the problems comes from the place where I deploy the validator classes. My application is deployed as an EAR. If I put the validator classes in WEB-INF/classes, the validator components don't start when Seam starts. If I put them in a JAR on EAR root, it works. Same thing with converters. Is there a reason for this ?

                            • 13. Re: How to write a validator that compares two fields
                              Olivier Thierry Apprentice

                              Sorry, I posted while you were talking about seam.properties ;)

                              For what I understood, you can use either seam.properties or components.xml, and I already have a components.xml file in WEB-INF. Do I have to declare the validator component in components.xml ? I thought you had not to do it if you have a @Name annotation in the class, but maybe I am wrong ?

                              • 14. Re: How to write a validator that compares two fields
                                Olivier Thierry Apprentice

                                I had an empty seam.properties file in WEB-INF/classes and it works. Thanks a lot for your help Pete.

                                1 2 Previous Next