-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 3 posts ] 
Author Message
 Post subject: More annotations
PostPosted: Thu Jun 30, 2005 4:23 pm 
Beginner
Beginner

Joined: Sat Oct 09, 2004 2:35 pm
Posts: 43
Location: Tenerife
Next I include a modified version of ClassValidator that uses Field level annotations and @Description to be added to InvalidValue bean:

ClassValidator

Code:
package org.hibernate.validator;

import org.hibernate.MappingException;

import org.hibernate.annotations.*;

import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;

import java.beans.Introspector;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.StringTokenizer;


/**
* Engine that take a bean and check every expressed annotation restrictions
* @author Gavin King
*/
public class ClassValidator<T> {

    private final Class<T> beanClass;
    private final List<Validator> beanValidators = new ArrayList<Validator>();
    private final List<Validator> propertyValidators =
        new ArrayList<Validator>();
    private final List<Method> propertyGetters = new ArrayList<Method>();
    private final List<Validator> propertyFieldsValidators =
        new ArrayList<Validator>();
    private final List<Field> propertyFields = new ArrayList<Field>();
    private final Map<Validator, String> messages =
        new HashMap<Validator, String>();

    private final ResourceBundle messageBundle;

    /**
     * create the validator engine for this bean type
     * @param beanClass bean Class to be validated
     */
    public ClassValidator(Class<T> beanClass) {
        this(beanClass, null);
    }

    /**
     * create the validator engine for a particular bean class, using a resource bundle
     * for message rendering on violation *
     * @param beanClass bean Class to be validated
     * @param resourceBundle The resource bundle to get messages from
     */
    public ClassValidator(Class<T> beanClass, ResourceBundle resourceBundle) {
        this.beanClass = beanClass;
        this.messageBundle = resourceBundle;

        Annotation[] classAnnotations = beanClass.getAnnotations();

        for (int i = 0; i < classAnnotations.length; i++) {
            Annotation classAnnotation = classAnnotations[i];
            Validator beanValidator = createValidator(classAnnotation);

            if (beanValidator != null)
                beanValidators.add(beanValidator);
        }

        Method[] methods = beanClass.getMethods();

        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            Annotation[] methodAnnotations = method.getAnnotations();

            for (int j = 0; j < methodAnnotations.length; j++) {
                Annotation methodAnnotation = methodAnnotations[j];
                Validator propertyValidator = createValidator(methodAnnotation);

                if (propertyValidator != null) {
                    propertyValidators.add(propertyValidator);
                    propertyGetters.add(method);
                }
            }
        }

        // Get annotations for all declared Fields
        Field[] fields = beanClass.getDeclaredFields();

        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            Annotation[] fieldAnnotations = field.getAnnotations();

            for (int j = 0; j < fieldAnnotations.length; j++) {
                Annotation fieldAnnotation = fieldAnnotations[j];
                Validator propertyFieldsValidator = createValidator(
                        fieldAnnotation);

                if (propertyFieldsValidator != null) {
                    propertyFieldsValidators.add(propertyFieldsValidator);
                    propertyFields.add(field);
                }
            }
        }
    }


//      private static final Set<String> methodNames = new HashSet<String>();
//      static {
//              methodNames.add("toString");
//              methodNames.add("hashCode");
//              methodNames.add("equals");
//              methodNames.add("annotationType");
//      }

    private Validator createValidator(Annotation annotation) {

        try {
            ValidatorClass validatorClass = annotation.annotationType()
                .getAnnotation(ValidatorClass.class);

            if (validatorClass == null)
                return null;

            Validator beanValidator = validatorClass.value().newInstance();
            beanValidator.initialize(annotation);

            String messageTemplate = (String) annotation.getClass()
                .getMethod("message").invoke(annotation);
            String message = replace(messageTemplate, annotation);
            messages.put(beanValidator, message);

            return beanValidator;
        } catch (Exception e) {
            throw new IllegalArgumentException(
                "could not instantiate ClassValidator", e);
        }
    }

    /**
     * apply constraints on a bean instance and return all the failures.
     * @param bean Bean object to validate
     * @return array of InvalidValues
     */
    public InvalidValue[] getInvalidValues(T bean) {
        return getInvalidValues(bean, null);
    }

    /**
     * apply constraints on a method from a bean instance and return all the failures.
     * @param bean Bean object to validate
     * @param method Method to validate
     * @return array of InvalidValues
     */
    public InvalidValue[] getInvalidValues(T bean, String method) {

        if (!beanClass.isInstance(bean)) {
            throw new IllegalArgumentException("not an instance of: " +
                bean.getClass());
        }

        List<InvalidValue> results = new ArrayList<InvalidValue>();

        if (method == null) {

            for (int i = 0; i < beanValidators.size(); i++) {
                Validator validator = beanValidators.get(i);

                if (!validator.isValid(bean)) {
                    results.add(new InvalidValue(messages.get(validator),
                            beanClass, null, bean, bean, null));
                }
            }
        }

        for (int i = 0; i < propertyValidators.size(); i++) {
            Method getter = propertyGetters.get(i);
            String propertyName = getPropertyName(getter);

            if ((method != null) && !method.equalsIgnoreCase(propertyName)) {
                continue;
            }

            Object value;

            try {
                value = getter.invoke(bean);
            } catch (Exception e) {
                throw new IllegalStateException("could not get property value",
                    e);
            }

            Validator validator = propertyValidators.get(i);

            if (!validator.isValid(value)) {
                String propertyDescription = getPropertyDescription(getter);
                results.add(new InvalidValue(messages.get(validator), beanClass,
                        propertyName, value, bean, propertyDescription));
            }
        }

        for (int i = 0; i < propertyFieldsValidators.size(); i++) {
            Field field = propertyFields.get(i);
            String propertyName = field.getName();

            if ((method != null) && !method.equalsIgnoreCase(propertyName)) {
                continue;
            }

            Object value;

            try {
                value = field.get(bean);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("could not get property value",
                    e);

                //} catch (java.lang.reflect.InvocationTargetException e) {
                //  throw new IllegalStateException("could not get property value",
                //    e);
            }

            Validator validator = propertyFieldsValidators.get(i);

            if (!validator.isValid(value)) {
                String propertyDescription = getFieldDescription(field);
                results.add(new InvalidValue(messages.get(validator), beanClass,
                        propertyName, value, bean, propertyDescription));
            }
        }

        return results.toArray(new InvalidValue[results.size()]);
    }

    private String getFieldDescription(Field field) {
        Description description = field.getAnnotation(Description.class);
        String retorno = null;

        if (description != null) {
            retorno = replace(description.value(), null);
        }

        return retorno;
    }

    private String getPropertyDescription(Method getter) {
        Description description = getter.getAnnotation(Description.class);
        String retorno = null;

        if (description != null) {
            retorno = replace(description.value(), null);
        }

        return retorno;
    }

    private String getPropertyName(Method getter) {
        String name = getter.getName();

        if (name.startsWith("is")) {
            name = name.substring(2);
        } else if (name.startsWith("get")) {
            name = name.substring(3);
        }

        String propertyName = Introspector.decapitalize(name);

        return propertyName;
    }

    private String replace(String message, Annotation parameters) {
        StringTokenizer tokens = new StringTokenizer(message, "{}", true);
        StringBuffer buf = new StringBuffer();
        boolean escaped = false;

        while (tokens.hasMoreTokens()) {
            String token = tokens.nextToken();

            if ("{".equals(token)) {
                escaped = true;
            } else if ("}".equals(token)) {
                escaped = false;
            } else if (!escaped) {
                buf.append(token);
            } else {
                Method member = null;

                if (parameters != null) {

                    try {
                        member = parameters.getClass().getMethod(token);
                    } catch (NoSuchMethodException nsfme) {
                        member = null;
                    }
                }

                if (member != null) {

                    try {
                        buf.append(member.invoke(parameters));
                    } catch (Exception e) {
                        throw new IllegalArgumentException(
                            "could not render message", e);
                    }
                } else if (messageBundle != null) {
                    String string = messageBundle.getString(token);

                    if (string != null)
                        buf.append(string);
                }
            }
        }

        return buf.toString();
    }

    /**
     * apply the registred constraints rules on the hibernate metadata (to be applied on DB schema...)
     * @param persistentClass hibernate metadata
     */
    public void apply(PersistentClass persistentClass) {

        Iterator<Validator> validators = beanValidators.iterator();

        while (validators.hasNext()) {
            Validator validator = validators.next();

            if (validator instanceof PersistentClassConstraint) {
                ((PersistentClassConstraint) validator).apply(persistentClass);
            }
        }

        validators = propertyValidators.iterator();

        Iterator<Method> getters = propertyGetters.iterator();

        while (validators.hasNext()) {
            Validator validator = validators.next();
            String propertyName = getPropertyName(getters.next());

            if (validator instanceof PropertyConstraint) {

                try {
                    Property property = persistentClass.getProperty(
                            propertyName);
                    ((PropertyConstraint) validator).apply(property);
                } catch (MappingException pnfe) {
                    //do nothing
                }
            }
        }

    }

}


InvalidValue
Code:
package org.hibernate.validator;

/**
* A single violation of a class level or method level constraint.
*
* @author Gavin King
*/
public class InvalidValue {
   private final String message;
   private final Object value;
   private final String propertyName;
   private final String propertyDescription;
   private final Class beanClass;
   private final Object bean;
   
   public InvalidValue(String message, Class beanClass, String propertyName, Object value, Object bean, String propertyDescription) {
      this.message = message;
      this.value = value;
      this.beanClass = beanClass;
      this.propertyName = propertyName;
      this.propertyDescription = propertyDescription;
      this.bean = bean;
   }
   
   public Class getBeanClass() {
      return beanClass;
   }
   
   public String getMessage() {
      return message;
   }

   public String getPropertyName() {
      return propertyName;
   }

   public String getPropertyDescription() {
      return propertyDescription;
   }

   public Object getValue() {
      return value;
   }

   public Object getBean() {
      return bean;
   }

   public String toString() {
      return propertyName + ' ' + message;
   }
   
}



Code:
package org.hibernate.annotations;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;

[b]Description annotation[/b]

/**
* Makes a Description about the annotated element.
* @author Jesus Marin
*/
@Target({ TYPE, METHOD, FIELD })
@Retention(RUNTIME)
public @interface Description {
    /**
     * value of description
     */
    String value();
}



CheckInList annotation
Code:
package org.hibernate.validator;

import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;



/**
* Defines a list of items to be used to CHECK a varchar field.
* @author Jesus Marin
*/
@ValidatorClass(CheckInListValidator.class)
@Target(METHOD)
@Retention(RUNTIME)
public @interface CheckInList {
    /**
     * array list to be used
     */
    String[] items();
    /**
     * default message
     */
    String message() default "Values are not allowed";
}


CheckInList validator
Code:
package org.hibernate.validator;

import org.hibernate.mapping.Column;
import org.hibernate.mapping.Property;


/**
* Do a check against a list of values.
*
* @author Jesus Marin
*/
public class CheckInListValidator implements Validator<CheckInList>,
    PropertyConstraint {
    private String[] values;

    public void initialize(CheckInList parameters) {
        values = parameters.items();
    }

    public boolean isValid(Object value) {
        boolean retorno = false;

        if (value == null) {
            retorno = true;
        } else if (!(value instanceof String)) {
            retorno = false;
        } else {
            String string = (String) value;

            for (int i = 0; i < values.length; i++) {

                if (string.equals(values[i])) {
                    retorno = true;
                }
            }
        }

        return retorno;
    }

    public void apply(Property property) {
        Column col = (Column) property.getColumnIterator().next();
        col.setCheckConstraint(col.getName() + " is not an allowed value");
    }

} // end class CheckInListValidator



The @Description idea was taken from Steeve and sounded very good to me. This way one can constraint a property and at the same time give an error according to the field description.

Besides I overloaded the ClassValidator getInvalidValues method in order to be able to check only one field constraint and not all at once.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 01, 2005 9:47 am 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
Post your patch (CVS patch) to JIRA please.

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jul 04, 2005 1:45 pm 
Beginner
Beginner

Joined: Sat Oct 09, 2004 2:35 pm
Posts: 43
Location: Tenerife
Done.
By the way, if you have a look at modified ClassValidator in Jira you wil see that now annotated validations can take place at field level. So it would be interesting to make org.hibernate.validator package available at FIELD level and not only at METHOD level.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 3 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.