5 Replies Latest reply on Nov 5, 2008 5:41 PM by francof

    JSF -> Hibernate validations, too much noise ; )

    hardaur

      Greetings,


        I'm writing an application with seam 2.1.0GA.  It's a war application, icefaces, mysql backend, etc.  I will have users (and using the new identity functionality), and need to validate that usernames are unique while the user is filling out the form.


        The form in question is basically a wizard based on a pageflow.  The first page is account information; username, email, password, verify, etc.  Second page demographic info, etc.  I'm not doing partialSubmit (the solution I'm looking for would work either way).  So user fills out the first page, clicks submit the validateAll tags go and validate everything against the hibernate constraints.  Great.  However, I can't seem to find a way to write my own hibernate validator that has access to entityManager for me to run a query, or any way to automate it quite as much.  I know I could write methods in an action controller, but I really don't feel that is the best, if even correct solution.  Obviously I do NOT want to wait until I persist the object at the end of registration (there are multiple entities in a graph involved).


      So. . how do you guys validate the uniqueness of your usernames.  I'm sure this is a problem just about every app has, and has solved.  But after so much searching and reading I can't find anything that makes any sense.  Just a lot of stuff telling me what the WRONG way to do it is. . .not very helpful ; )


      Thanks!


      H


      P.S.  This will also be the same for email.  A @Unique hibernate constraint is what is really needed, but it seems to be a bad idea for some reason I haven't quite wrapped my head around.

        • 1. Re: JSF -> Hibernate validations, too much noise ; )

          Gerald,


          I am a Hibernate newbie.


          I ran into the same issue when I was looking for a solution. I built a custom Hibernate validator, it has been working for me. It is a generic solution and can be applied on a single column that you are checking uniqueness for - not sure it is the most elegant solution but here it is.


          @Target({ METHOD, FIELD })
          @Retention(RUNTIME)
          @Documented
          @ValidatorClass(UniqueSimpleKeyValidator.class)
          public @interface UniqueSimpleKey {
               String tableName() ;
               
               String pkColumnName();
               
               String message() default "This code already exists";
          }
          




          public class UniqueSimpleKeyValidator implements Validator<UniqueSimpleKey>, Serializable {
          
               private static final long serialVersionUID = 1L;
               private String tableName ;
               private String pkColumnName;
               
               public void initialize(UniqueSimpleKey parameters) {
                  tableName = parameters.tableName();
                  tableName = ( tableName == null ) ? ""  : tableName.trim();
                  
                  pkColumnName = parameters.pkColumnName();
                  pkColumnName = ( pkColumnName == null ) ? ""  : pkColumnName.trim();
                  
               }
          
              
               
               public boolean isValid(Object value) {
                   //System.out.println("\n\n In UniqueSimpleKeyValidator");
                   // Ignore checks for required - they should be handled by other validators.
                  if (value == null) {
                      return true;
                  }
                  
                  //TODO UniqueSimpleKeyValidator - Allow for this test on integer, date PK values  
                  
                  String stringValue = new String();
                  /*Integer intValue = 0; */
                  
                  
                  if (value instanceof String) {
                      stringValue = (String) value;
                      stringValue = stringValue.trim();
                      
                      if (stringValue.trim().length() == 0) {
                          return true;
                      }
                  }
                  
                  if ( tableName.length() == 0 || pkColumnName.length() == 0 )
                       return false;
                  
                  EntityManager em = (EntityManager) Component.getInstance("entityManager");
                  String query = "SELECT " + pkColumnName + " FROM " + tableName + " WHERE " + pkColumnName + " = ?1";
                  
                    try {
                         Query qryObject = em.createNativeQuery(query);
          
                         // START check for pkColumnName of string type
                         
                         qryObject.setParameter(1, stringValue);
          
                         String oldValue = new String(); 
                              
                         oldValue = (String)qryObject.getSingleResult();
                         
                         if (oldValue.equals(stringValue))
                              return true;
                         
                         // END check for pkColumnName of string type
                         
                         // This constraint will be checked on add & edits of this entity. It will also be fired when
                         // Hibernate attempts to save or update the entity.
                         
                         // At the database level, the field should have a unique constraint.
                          
                    } catch (NoResultException nre) {
                         return true;
                    }
          
                  return true;
              }
          
          
          }
          



          Then on your entity column, you can use this constraint like so


               @Column(name = "USER_NAME")
               @Length(max = 10)
               @UniqueSimpleKey (tableName = "APPUSER", pkColumnName = "USER_NAME", message="This user already exists")
          





          Best regards
          Franco




          • 2. Re: JSF -> Hibernate validations, too much noise ; )
            jguglielmin

            Not sure why you wouldn't want to use the partialSubmit or the validator property on the input component, so will show a way to use these.  For example, you don't want to allow a user to use an already registered username.


                 <s:decorate id="usernameField" template="layout/edit.xhtml">
                       <ui:definename="label">Username</ui:define>
                       <ice:inputText id="username"   
                              value="#{newUser.username}"                          
                              validator="#{registerAction.validateUserName}"
                           size="20" required="true" partialSubmit="true"/>                
                     </s:decorate>



            and the code behind the validation would be


                 public void validateUserName(FacesContext context, UIComponent validate, Object value){
                      //since we have our own validator, need to check for hibernate annotation validations
                      checkHibernateAnnotations(context, validate, value);          
                      String username=(String)value;
                      if (!isUsernameAvailable(username)) {
                           FacesMessage msg = new FacesMessage("username already taken");
                           context.addMessage(validate.getClientId(context), msg);
                           usernameValid=false;
                      }else usernameValid=true;
                 }



            With the following helper methods.


                 public boolean isUsernameAvailable(String username) {
                      return entityManager.createQuery(
                           "select m from Member m where m.username = ?1")
                           .setParameter(1, username).getResultList().size() == 0;
                 }




                 private void checkHibernateAnnotations(FacesContext context,
                           UIComponent validate, Object value) {
                      ModelValidator mv = new ModelValidator();
                      mv.validate(context, validate, value);
                 }



            This last method is required to check the hibernate annotations on the entity class for user since the ModelValidator class is not normally fired at this time.  In this case the hibernate annotation used may be length, but you could use any of the other annotations for email, etc to combine whatever facility hibernate has as well as writing your own validation in the validator method.  The ModelValidator is part of the seam ui library.  Then as your user enters information, it is checked (via ajax) at each component to ensure the entry is correct, rather than just at form submit.


            • 3. Re: JSF -> Hibernate validations, too much noise ; )
              hardaur

              Franco, that's almost exactly what I was trying to do, I just couldn't get a good instance of the entityManager in my validator class.


              However,


              I did a little more testing and found out what would suit my purposes best, I think. 


              I didn't want to use the icefaces validator tag for exactly the reason you mention Judy; that it would bypass the hibernate validations afterwards.  I hadn't found a way around that (thank you).  But, I did go back to the valueChangeListener and without doing partialSubmit it works like a charm (and hits the hibernate validators).  So. . .




              <s:decorate id="usernameDecoration" template="/layout/edit.xhtml">
                                          <ui:define name="label">Username</ui:define>
                                          <ice:inputText id="username"
                                                         required="true"
                                                         length="64"
                                                         value="#{user.username}"
                                                         valueChangeListener="#{registrationWizard.validateUniqueUsername}"/>



              and



              public void validateUniqueUsername(ValueChangeEvent e)
                   {
                        String username = (String) e.getNewValue();
              
                        int num = entityManager.createQuery(
                             "select u.username from User u where u.username = :username")
                             .setParameter("username", username).getResultList().size();
              
                        if (num != 0)
                        {
                             facesMessages.addToControl(e.getComponent().getId(),
                                  "Username already used, please choose another!");
                        }
                   }




              I didn't want to do partialSubmits becaus this will hopefully be a very high traffic site and I didn't want that much trivial traffic.  This way, I'm only verifying on submit of each page of the wizard.


              Thanks, learned something valuable from both of you!



              G

              • 4. Re: JSF -> Hibernate validations, too much noise ; )

                Please disregard my solution.


                I am having issues myself. It turns out my custom validation worked fine when I edit an entity. When I tried to create a new entity, the validation caused all sorts of headaches for me.
                I was getting this -


                org.hibernate.HibernateException: Found two representations of same collection: com.cmsc.cast.model.Tax.items
                     at org.hibernate.engine.Collections.processReachableCollection(Collections.java:153)
                



                I know you are thinking that is something else.
                Debugging through my code, I realized that the culprit was my custom validator. It seems to me like it has to do with the Entity manager instance not being cleared after the validator completes.  When I do not use this validator, my inserts work fine.


                I prefer keeping all validations on the model, so I will try to give it another day. If I can't figure it out, I will have to go with Judy's solution too.


                Thanks


                Franco

                • 5. Re: JSF -> Hibernate validations, too much noise ; )

                  I resolved it.


                  After line


                  Query qryObject = em.createNativeQuery(query);
                  




                  I addded line


                  qryObject.setFlushMode(FlushModeType.COMMIT);