6 Replies Latest reply on Feb 24, 2010 8:25 AM by Joshua D

    Entity level validation

    Franco Fernandes Novice

      I asked this on the Hibernate forum, trying here.


      I want to validate some parts of my entity, these cannot be validated using Hibernate validators. For instance, a start date less than end date as an example.


      What I want to achieve is have the validation errors to be available to Seam (FacesMessages) and also part of Hibernate InvalidValues


      I am using EntityHome, so I could have just done this


      public String persist() {
      //get EntityHome entity instance


      //validate date range, if error add message to FacesMessage
      }


      However, I may also need to use my entity outside of the View layer (for data loads), so I was trying to centralize the validations.



      How do I put it in on the entity ?


      Can I do something in the entity's preUpdate listener?



      @PreUpdate
      public void preUpdate() {
         // how I do it here  ?
        // validate date range
        // validate something else
        
       // If there are errors add can I add to InvalidValues[] 
       // so that org.hibernate.validator.event.ValidateEventListener.validate can find it
      }
      


           


      If there is another approach, perhaps a custom Hibernate validator. Just looking for some guidance here.


      Thanks





        • 1. Re: Entity level validation
          Edwin van der Elst Newbie

          A Class level validator would be the cleanest solution.
          However, Seam doesn't evaluatie those validations on a form submit.
          Hibernate will throw an exception during the persist if such a validator fails...


          What you can do is the following:



          1. create a validator

          2. call the validator before the persist method

          3. add any invalid values to the FacesMessage and return null (forces re-display of the page)



          Example:


          @Target(ElementType.TYPE)
          @Retention(RetentionPolicy.RUNTIME)
          @ValidatorClass(PersonValidatorImpl.class)
          public @interface PersonValidator {
             String message() default "person validation fails";
          }




          public class PersonValidatorImpl implements Validator<PersonValidator> {
          
             public void initialize(PersonValidator arg0) {
             }
          
             public boolean isValid(Object arg0) {
                Person p = (Person) arg0;
                if (p.getName().contains("edwin")) {
                   return false;
                }
                return true;
             }
          }



          In PersonHome (assuming you subclass EntityHome)


            @Override
             public String persist() {
                ClassValidator<Person> validator = new ClassValidator<Person>(Person.class);
                InvalidValue[] invalidValues = validator.getInvalidValues(this.instance);
                if (invalidValues.length>0) {
                   for (InvalidValue v : invalidValues) {
                      FacesMessages.instance().add(v);
                   }
                   return null;
                }
                return super.persist();
             }



          Don't forget to put a <h:messages globalOnly=true> in the template to display the validation errors!

          • 2. Re: Entity level validation
            Franco Fernandes Novice

            Edwin,


            This is exactly what I finished with yesterday :)


            Thank you very much for taking the time to help, I really appreciate it.



            Regards


            Franco


             

            • 3. Re: Entity level validation
              Franco Fernandes Novice

              My only beef with this approach is that I cannot create custom messages (or maybe I don't know how) for each failed validation.


              @CatalogItemValidator( message = "Validation failed while inserting/updating this entity")
              public class CatalogItem { .. }
              




              public @interface CatalogItemValidator {
                   String message() default "{validator.catalogitem.invalid}";
              }
              



              public class CatalogItemValidatorImpl implements Validator<CatalogItemValidator>, Serializable {
                   public boolean isValid(Object value) {
                   
                        boolean isValid = true;
                        CatalogItem catalogItem = (CatalogItem) value;
                        
                        java.util.Date startDt = (java.util.Date) catalogItem.getFeatureStartDate();
                        java.util.Date endDt = (java.util.Date) catalogItem.getFeatureEndDate();
                        if ( startDt != null && endDt != null ) {
                             isValid = endDt.after(startDt) || endDt.equals(startDt) ;
                             ////want custom message here
                        }
                        
                        startDt = (java.util.Date) catalogItem.getWebStartDate();
                        endDt = (java.util.Date) catalogItem.getWebEndDate();
                        if ( startDt != null && endDt != null ) {
                             isValid = endDt.after(startDt) || endDt.equals(startDt) ;
                             ////want custom message here
                        }
                   
                        return isValid; 
                   }
              }
              



              I always get the message that I sent in as the parameter. It gives me no clue as to which field failed the validation.


              One approach I thought of was to declare a transient variable on the entity itself.


              @Entity
              public class CatalogItem {
              
              List<InvalidValue> errors = new ArrayList<InvalidValue>();
              
              //getters, setters
              
              }
              



              then from the custom validator store all the errors here.


              Does anyone have any other solutions ?



              Thanks
              Franco


              • 4. Re: Entity level validation
                Joshua D Novice

                We are also looking for raising custom messages for different conditions, is there a cleaner way to achieve that from within the class level validator.

                • 5. Re: Entity level validation
                  Serkan Eskici Novice

                  What you also could do in this case is the following in your Entity Bean:



                  //this is a Hibernate Validator on your method
                  @AssertTrue(message="date1 must be less than date2")
                  public boolean validateDate() {
                     if(date1 != null && date2 != null) 
                        return date1.compareTo(date2) < 0;
                  
                     return true;
                  }
                  





                  if you've annotated your fields in stead of your getters in your Entity Bean, then you'll have to add a boolean field with @AssertTrue on it and implement this logic in the getter.


                  Another alternative:
                  http://seamframework.org/Community/ComponentgetInstanceInsidedAPrePersistMethod

                  • 6. Re: Entity level validation
                    Joshua D Novice

                    I have already tried your suggestion before, by adding @AssertTrue and it still fails because it is not bound to the Entity member variable and it doesn't get surfaced to the UI.


                    For the time being we have moved these checks to EntityHome.persist / update methods