org.libreplan.business.common.LibrePlanClassValidator.java Source code

Java tutorial

Introduction

Here is the source code for org.libreplan.business.common.LibrePlanClassValidator.java

Source

/*
 * This file is part of LibrePlan
 *
 * Copyright (C) 2010-2011 Igalia, S.L.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.libreplan.business.common;

import java.beans.Introspector;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.StringTokenizer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.AssertionFailure;
import org.hibernate.Hibernate;
import org.hibernate.MappingException;
import org.hibernate.annotations.common.reflection.Filter;
import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XMember;
import org.hibernate.annotations.common.reflection.XMethod;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.annotations.common.reflection.java.JavaReflectionManager;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.util.IdentitySet;
import org.hibernate.validator.InvalidStateException;
import org.hibernate.validator.InvalidValue;
import org.hibernate.validator.MessageInterpolator;
import org.hibernate.validator.PersistentClassConstraint;
import org.hibernate.validator.PropertyConstraint;
import org.hibernate.validator.Valid;
import org.hibernate.validator.Validator;
import org.hibernate.validator.ValidatorClass;
import org.hibernate.validator.Version;
import org.hibernate.validator.interpolator.DefaultMessageInterpolatorAggerator;

/**
 * This class is copy-cat of HibernateValidator.ClassValidator
 *
 * Adds extra functionality to cache extra ClassValidators created for validating children elements (see getClassValidator)
 *
 * The function createChildValidator creates ClassValidators for getters and members marked for Validation, but it doesn't create
 * ClassValidators for members that return Collections or ArrayList of entities. These ClassValidators are created later in getClasValidator
 * but are not cached.
 *
 * Original code: http://anonsvn.jboss.org/repos/hibernate/validator/trunk/hibernate-validator-legacy/src/main/java/org/hibernate/validator/ClassValidator.java
 *
 * @author Diego Pino <dpino@igalia.com>
 */
public class LibrePlanClassValidator<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    private static Log log = LogFactory.getLog(LibrePlanClassValidator.class);
    private static final InvalidValue[] EMPTY_INVALID_VALUE_ARRAY = new InvalidValue[] {};
    private static final String DEFAULT_VALIDATOR_MESSAGE = "org.hibernate.validator.resources.DefaultValidatorMessages";
    private static final String VALIDATOR_MESSAGE = "ValidatorMessages";
    private static final Set<Class> INDEXABLE_CLASS = new HashSet<Class>();

    static {
        INDEXABLE_CLASS.add(Integer.class);
        INDEXABLE_CLASS.add(Long.class);
        INDEXABLE_CLASS.add(String.class);
    }

    static {
        Version.touch(); //touch version
    }

    private final Class<T> beanClass;
    private transient ResourceBundle messageBundle;
    private transient ResourceBundle defaultMessageBundle;
    private transient boolean isUserProvidedResourceBundle;
    private transient ReflectionManager reflectionManager;

    private final transient Map<XClass, LibrePlanClassValidator> childClassValidators;
    private final transient Map<XClass, LibrePlanClassValidator> extraClassValidators;
    private transient List<Validator> beanValidators;
    private transient List<Validator> memberValidators;
    private transient List<XMember> memberGetters;
    private transient List<XMember> childGetters;
    private transient DefaultMessageInterpolatorAggerator defaultInterpolator;
    private transient MessageInterpolator userInterpolator;
    private static final Filter GET_ALL_FILTER = new Filter() {
        public boolean returnStatic() {
            return true;
        }

        public boolean returnTransient() {
            return true;
        }
    };

    /**
     * create the validator engine for this bean type
     */
    public LibrePlanClassValidator(Class<T> beanClass) {
        this(beanClass, (ResourceBundle) null);
    }

    /**
     * create the validator engine for a particular bean class, using a resource bundle
     * for message rendering on violation
     */
    public LibrePlanClassValidator(Class<T> beanClass, ResourceBundle resourceBundle) {
        this(beanClass, resourceBundle, null, new HashMap<XClass, LibrePlanClassValidator>(), null);
    }

    /**
     * create the validator engine for a particular bean class, using a custom message interpolator
     * for message rendering on violation
     */
    public LibrePlanClassValidator(Class<T> beanClass, MessageInterpolator interpolator) {
        this(beanClass, null, interpolator, new HashMap<XClass, LibrePlanClassValidator>(), null);
    }

    /**
     * Not a public API
     */
    public LibrePlanClassValidator(Class<T> beanClass, ResourceBundle resourceBundle,
            MessageInterpolator interpolator, Map<XClass, LibrePlanClassValidator> childClassValidators,
            ReflectionManager reflectionManager) {
        this.reflectionManager = reflectionManager != null ? reflectionManager : new JavaReflectionManager();
        XClass beanXClass = this.reflectionManager.toXClass(beanClass);
        this.beanClass = beanClass;
        this.messageBundle = resourceBundle == null ? getDefaultResourceBundle() : resourceBundle;
        this.defaultMessageBundle = ResourceBundle.getBundle(DEFAULT_VALIDATOR_MESSAGE);
        this.userInterpolator = interpolator;
        this.childClassValidators = childClassValidators != null ? childClassValidators
                : new HashMap<XClass, LibrePlanClassValidator>();
        this.extraClassValidators = new HashMap<XClass, LibrePlanClassValidator>();
        initValidator(beanXClass, this.childClassValidators);
    }

    @SuppressWarnings("unchecked")
    protected LibrePlanClassValidator(XClass beanXClass, ResourceBundle resourceBundle,
            MessageInterpolator userInterpolator, Map<XClass, LibrePlanClassValidator> childClassValidators,
            ReflectionManager reflectionManager) {
        this.reflectionManager = reflectionManager;
        this.beanClass = reflectionManager.toClass(beanXClass);
        this.messageBundle = resourceBundle == null ? getDefaultResourceBundle() : resourceBundle;
        this.defaultMessageBundle = ResourceBundle.getBundle(DEFAULT_VALIDATOR_MESSAGE);
        this.userInterpolator = userInterpolator;
        this.childClassValidators = childClassValidators;
        this.extraClassValidators = new HashMap<XClass, LibrePlanClassValidator>();
        initValidator(beanXClass, childClassValidators);
    }

    private ResourceBundle getDefaultResourceBundle() {
        ResourceBundle rb;
        try {
            //use context class loader as a first citizen
            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
            if (contextClassLoader == null) {
                throw new MissingResourceException("No context classloader", null, VALIDATOR_MESSAGE);
            }
            rb = ResourceBundle.getBundle(VALIDATOR_MESSAGE, Locale.getDefault(), contextClassLoader);
        } catch (MissingResourceException e) {
            log.trace("ResourceBundle " + VALIDATOR_MESSAGE + " not found in thread context classloader");
            //then use the Validator Framework classloader
            try {
                rb = ResourceBundle.getBundle(VALIDATOR_MESSAGE, Locale.getDefault(),
                        this.getClass().getClassLoader());
            } catch (MissingResourceException ee) {
                log.debug("ResourceBundle ValidatorMessages not found in Validator classloader. Delegate to "
                        + DEFAULT_VALIDATOR_MESSAGE);
                //the user did not override the default ValidatorMessages
                rb = null;
            }
        }
        isUserProvidedResourceBundle = true;
        return rb;
    }

    private void initValidator(XClass xClass, Map<XClass, LibrePlanClassValidator> childClassValidators) {
        beanValidators = new ArrayList<Validator>();
        memberValidators = new ArrayList<Validator>();
        memberGetters = new ArrayList<XMember>();
        childGetters = new ArrayList<XMember>();
        defaultInterpolator = new DefaultMessageInterpolatorAggerator();
        defaultInterpolator.initialize(messageBundle, defaultMessageBundle);

        //build the class hierarchy to look for members in
        childClassValidators.put(xClass, this);
        Collection<XClass> classes = new HashSet<XClass>();
        addSuperClassesAndInterfaces(xClass, classes);
        for (XClass currentClass : classes) {
            Annotation[] classAnnotations = currentClass.getAnnotations();
            for (int i = 0; i < classAnnotations.length; i++) {
                Annotation classAnnotation = classAnnotations[i];
                Validator beanValidator = createValidator(classAnnotation);
                if (beanValidator != null)
                    beanValidators.add(beanValidator);
                handleAggregateAnnotations(classAnnotation, null);
            }
        }

        //Check on all selected classes
        for (XClass currClass : classes) {
            List<XMethod> methods = currClass.getDeclaredMethods();
            for (XMethod method : methods) {
                createMemberValidator(method);
                createChildValidator(method);
            }

            List<XProperty> fields = currClass.getDeclaredProperties("field", GET_ALL_FILTER);
            for (XProperty field : fields) {
                createMemberValidator(field);
                createChildValidator(field);
            }
        }
    }

    private void addSuperClassesAndInterfaces(XClass clazz, Collection<XClass> classes) {
        for (XClass currClass = clazz; currClass != null; currClass = currClass.getSuperclass()) {
            if (!classes.add(currClass))
                return;
            XClass[] interfaces = currClass.getInterfaces();
            for (XClass interf : interfaces) {
                addSuperClassesAndInterfaces(interf, classes);
            }
        }
    }

    private boolean handleAggregateAnnotations(Annotation annotation, XMember member) {
        Object[] values;
        try {
            Method valueMethod = annotation.getClass().getMethod("value");
            if (valueMethod.getReturnType().isArray()) {
                values = (Object[]) valueMethod.invoke(annotation);
            } else {
                return false;
            }
        } catch (NoSuchMethodException e) {
            return false;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }

        boolean validatorPresent = false;
        for (Object value : values) {
            if (value instanceof Annotation) {
                annotation = (Annotation) value;
                Validator validator = createValidator(annotation);
                if (validator != null) {
                    if (member != null) {
                        //member
                        memberValidators.add(validator);
                        setAccessible(member);
                        memberGetters.add(member);
                    } else {
                        //bean
                        beanValidators.add(validator);
                    }
                    validatorPresent = true;
                }
            }
        }
        return validatorPresent;
    }

    @SuppressWarnings("unchecked")
    private void createChildValidator(XMember member) {
        if (member.isAnnotationPresent(Valid.class)) {
            setAccessible(member);
            childGetters.add(member);
            XClass clazz;
            if (member.isCollection() || member.isArray()) {
                clazz = member.getElementClass();
            } else {
                clazz = member.getType();
            }
            if (!childClassValidators.containsKey(clazz)) {
                //ClassValidator added by side effect (added to childClassValidators during CV construction)
                new LibrePlanClassValidator(clazz, messageBundle, userInterpolator, childClassValidators,
                        reflectionManager);
            }
        }
    }

    private void createMemberValidator(XMember member) {
        boolean validatorPresent = false;
        Annotation[] memberAnnotations = member.getAnnotations();
        for (Annotation methodAnnotation : memberAnnotations) {
            Validator propertyValidator = createValidator(methodAnnotation);
            if (propertyValidator != null) {
                memberValidators.add(propertyValidator);
                setAccessible(member);
                memberGetters.add(member);
                validatorPresent = true;
            }
            boolean agrValidPresent = handleAggregateAnnotations(methodAnnotation, member);
            validatorPresent = validatorPresent || agrValidPresent;
        }
        if (validatorPresent && !member.isTypeResolved()) {
            log.warn("Original type of property " + member + " is unbound and has been approximated.");
        }
    }

    private static void setAccessible(XMember member) {
        if (!Modifier.isPublic(member.getModifiers())) {
            member.setAccessible(true);
        }
    }

    @SuppressWarnings("unchecked")
    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);
            defaultInterpolator.addInterpolator(annotation, beanValidator);
            return beanValidator;
        } catch (Exception e) {
            throw new IllegalArgumentException("could not instantiate LibrePlanClassValidator", e);
        }
    }

    public boolean hasValidationRules() {
        return beanValidators.size() != 0 || memberValidators.size() != 0;
    }

    /**
     * apply constraints on a bean instance and return all the failures.
     * if <code>bean</code> is null, an empty array is returned
     */
    public InvalidValue[] getInvalidValues(T bean) {
        return this.getInvalidValues(bean, new IdentitySet());
    }

    /**
     * apply constraints on a bean instance and return all the failures.
     * if <code>bean</code> is null, an empty array is returned
     */
    @SuppressWarnings("unchecked")
    protected InvalidValue[] getInvalidValues(T bean, Set<Object> circularityState) {
        if (bean == null || circularityState.contains(bean)) {
            return EMPTY_INVALID_VALUE_ARRAY; //Avoid circularity
        } else {
            circularityState.add(bean);
        }

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

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

        for (int i = 0; i < beanValidators.size(); i++) {
            Validator validator = beanValidators.get(i);
            if (!validator.isValid(bean)) {
                results.add(new InvalidValue(interpolate(validator), beanClass, null, bean, bean));
            }
        }

        for (int i = 0; i < memberValidators.size(); i++) {
            XMember getter = memberGetters.get(i);
            if (Hibernate.isPropertyInitialized(bean, getPropertyName(getter))) {
                Object value = getMemberValue(bean, getter);
                Validator validator = memberValidators.get(i);
                if (!validator.isValid(value)) {
                    String propertyName = getPropertyName(getter);
                    results.add(new InvalidValue(interpolate(validator), beanClass, propertyName, value, bean));
                }
            }
        }

        for (int i = 0; i < childGetters.size(); i++) {
            XMember getter = childGetters.get(i);
            if (Hibernate.isPropertyInitialized(bean, getPropertyName(getter))) {
                Object value = getMemberValue(bean, getter);
                if (value != null && Hibernate.isInitialized(value)) {
                    String propertyName = getPropertyName(getter);
                    if (getter.isCollection()) {
                        int index = 0;
                        boolean isIterable = value instanceof Iterable;
                        Map map = !isIterable ? (Map) value : null;
                        Iterable elements = isIterable ? (Iterable) value : map.keySet();
                        for (Object element : elements) {
                            Object actualElement = isIterable ? element : map.get(element);
                            if (actualElement == null) {
                                index++;
                                continue;
                            }
                            InvalidValue[] invalidValues = getClassValidator(actualElement)
                                    .getInvalidValues(actualElement, circularityState);

                            String indexedPropName = MessageFormat.format("{0}[{1}]", propertyName,
                                    INDEXABLE_CLASS.contains(element.getClass()) ? ("'" + element + "'") : index);
                            index++;

                            for (InvalidValue invalidValue : invalidValues) {
                                invalidValue.addParentBean(bean, indexedPropName);
                                results.add(invalidValue);
                            }
                        }
                    }
                    if (getter.isArray()) {
                        int index = 0;
                        for (Object element : (Object[]) value) {
                            if (element == null) {
                                index++;
                                continue;
                            }
                            InvalidValue[] invalidValues = getClassValidator(element).getInvalidValues(element,
                                    circularityState);

                            String indexedPropName = MessageFormat.format("{0}[{1}]", propertyName, index);
                            index++;

                            for (InvalidValue invalidValue : invalidValues) {
                                invalidValue.addParentBean(bean, indexedPropName);
                                results.add(invalidValue);
                            }
                        }
                    } else {
                        InvalidValue[] invalidValues = getClassValidator(value).getInvalidValues(value,
                                circularityState);
                        for (InvalidValue invalidValue : invalidValues) {
                            invalidValue.addParentBean(bean, propertyName);
                            results.add(invalidValue);
                        }
                    }
                }
            }
        }

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

    private String interpolate(Validator validator) {
        String message = defaultInterpolator.getAnnotationMessage(validator);
        if (userInterpolator != null) {
            return userInterpolator.interpolate(message, validator, defaultInterpolator);
        } else {
            return defaultInterpolator.interpolate(message, validator, null);
        }
    }

    @SuppressWarnings("unchecked")
    private LibrePlanClassValidator getClassValidator(Object value) {
        Class clazz = value.getClass();
        XClass xclass = reflectionManager.toXClass(clazz);
        LibrePlanClassValidator validator = (LibrePlanClassValidator) childClassValidators.get(xclass);
        if (validator == null) { //handles polymorphism
            validator = extraClassValidators.get(xclass);
            if (validator == null) {
                validator = new LibrePlanClassValidator(clazz);
                extraClassValidators.put(xclass, validator);
            }
        }
        return validator;
    }

    /**
     * Apply constraints of a particular property on a bean instance and return all the failures.
     * Note this is not recursive.
     */
    //TODO should it be recursive?
    public InvalidValue[] getInvalidValues(T bean, String propertyName) {
        List<InvalidValue> results = new ArrayList<InvalidValue>();

        for (int i = 0; i < memberValidators.size(); i++) {
            XMember getter = memberGetters.get(i);
            if (getPropertyName(getter).equals(propertyName)) {
                Object value = getMemberValue(bean, getter);
                Validator validator = memberValidators.get(i);
                if (!validator.isValid(value)) {
                    results.add(new InvalidValue(interpolate(validator), beanClass, propertyName, value, bean));
                }
            }
        }

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

    /**
     * Apply constraints of a particular property value of a bean type and return all the failures.
     * The InvalidValue objects returns return null for InvalidValue#getBean() and InvalidValue#getRootBean()
     * Note this is not recursive.
     */
    //TODO should it be recursive?
    public InvalidValue[] getPotentialInvalidValues(String propertyName, Object value) {
        List<InvalidValue> results = new ArrayList<InvalidValue>();

        for (int i = 0; i < memberValidators.size(); i++) {
            XMember getter = memberGetters.get(i);
            if (getPropertyName(getter).equals(propertyName)) {
                Validator validator = memberValidators.get(i);
                if (!validator.isValid(value)) {
                    results.add(new InvalidValue(interpolate(validator), beanClass, propertyName, value, null));
                }
            }
        }

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

    private Object getMemberValue(T bean, XMember getter) {
        Object value;
        try {
            value = getter.invoke(bean);
        } catch (Exception e) {
            throw new IllegalStateException("Could not get property value", e);
        }
        return value;
    }

    public String getPropertyName(XMember member) {
        //Do no try to cache the result in a map, it's actually much slower (2.x time)
        String propertyName;
        if (XProperty.class.isAssignableFrom(member.getClass())) {
            propertyName = member.getName();
        } else if (XMethod.class.isAssignableFrom(member.getClass())) {
            propertyName = member.getName();
            if (propertyName.startsWith("is")) {
                propertyName = Introspector.decapitalize(propertyName.substring(2));
            } else if (propertyName.startsWith("get")) {
                propertyName = Introspector.decapitalize(propertyName.substring(3));
            }
            //do nothing for non getter method, in case someone want to validate a PO Method
        } else {
            throw new AssertionFailure("Unexpected member: " + member.getClass().getName());
        }
        return propertyName;
    }

    /** @deprecated */
    private String replace(String message, Annotation parameters) {
        StringTokenizer tokens = new StringTokenizer(message, "#{}", true);
        StringBuilder buf = new StringBuilder(30);
        boolean escaped = false;
        boolean el = false;
        while (tokens.hasMoreTokens()) {
            String token = tokens.nextToken();
            if (!escaped && "#".equals(token)) {
                el = true;
            }
            if (!el && "{".equals(token)) {
                escaped = true;
            } else if (escaped && "}".equals(token)) {
                escaped = false;
            } else if (!escaped) {
                if ("{".equals(token))
                    el = false;
                buf.append(token);
            } else {
                Method member;
                try {
                    member = parameters.getClass().getMethod(token, (Class[]) null);
                } 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 {
                    String string = null;
                    try {
                        string = messageBundle != null ? messageBundle.getString(token) : null;
                    } catch (MissingResourceException e) {
                        //give a second chance with the default resource bundle
                    }
                    if (string == null) {
                        try {
                            string = defaultMessageBundle.getString(token);
                        } catch (MissingResourceException e) {
                            throw new MissingResourceException(
                                    "Can't find resource in validator bundles, key " + token,
                                    defaultMessageBundle.getClass().getName(), token);
                        }
                    }
                    if (string != null)
                        buf.append(replace(string, parameters));
                }
            }
        }
        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) {

        for (Validator validator : beanValidators) {
            if (validator instanceof PersistentClassConstraint) {
                ((PersistentClassConstraint) validator).apply(persistentClass);
            }
        }

        Iterator<Validator> validators = memberValidators.iterator();
        Iterator<XMember> getters = memberGetters.iterator();
        while (validators.hasNext()) {
            Validator validator = validators.next();
            String propertyName = getPropertyName(getters.next());
            if (validator instanceof PropertyConstraint) {
                try {
                    Property property = findPropertyByName(persistentClass, propertyName);
                    if (property != null) {
                        ((PropertyConstraint) validator).apply(property);
                    }
                } catch (MappingException pnfe) {
                    //do nothing
                }
            }
        }

    }

    public void assertValid(T bean) {
        InvalidValue[] values = getInvalidValues(bean);
        if (values.length > 0) {
            throw new InvalidStateException(values);
        }
    }

    private void writeObject(ObjectOutputStream oos) throws IOException {
        ResourceBundle rb = messageBundle;
        MessageInterpolator interpolator = this.userInterpolator;
        if (rb != null && !(rb instanceof Serializable)) {
            messageBundle = null;
            if (!isUserProvidedResourceBundle) {
                log.warn(
                        "Serializing a LibrePlanClassValidator with a non serializable ResourceBundle: ResourceBundle ignored");
            }
        }
        if (interpolator != null && !(interpolator instanceof Serializable)) {
            userInterpolator = null;
            log.warn("Serializing a non serializable MessageInterpolator");
        }
        oos.defaultWriteObject();
        oos.writeObject(messageBundle);
        oos.writeObject(userInterpolator);
        messageBundle = rb;
        userInterpolator = interpolator;
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        ResourceBundle rb = (ResourceBundle) ois.readObject();
        if (rb == null)
            rb = getDefaultResourceBundle();
        this.messageBundle = rb;
        this.userInterpolator = (MessageInterpolator) ois.readObject();
        this.defaultMessageBundle = ResourceBundle.getBundle(DEFAULT_VALIDATOR_MESSAGE);
        reflectionManager = new JavaReflectionManager();
        initValidator(reflectionManager.toXClass(beanClass), new HashMap<XClass, LibrePlanClassValidator>());
    }

    /**
     * Retrieve the property by path in a recursive way, including IndetifierProperty in the loop
     * If propertyName is null or empty, the IdentifierProperty is returned
     */
    public static Property findPropertyByName(PersistentClass associatedClass, String propertyName) {
        Property property = null;
        Property idProperty = associatedClass.getIdentifierProperty();
        String idName = idProperty != null ? idProperty.getName() : null;
        try {
            if (propertyName == null || propertyName.length() == 0 || propertyName.equals(idName)) {
                //default to id
                property = idProperty;
            } else {
                if (propertyName.indexOf(idName + ".") == 0) {
                    property = idProperty;
                    propertyName = propertyName.substring(idName.length() + 1);
                }
                StringTokenizer st = new StringTokenizer(propertyName, ".", false);
                while (st.hasMoreElements()) {
                    String element = (String) st.nextElement();
                    if (property == null) {
                        property = associatedClass.getProperty(element);
                    } else {
                        if (!property.isComposite())
                            return null;
                        property = ((Component) property.getValue()).getProperty(element);
                    }
                }
            }
        } catch (MappingException e) {
            try {
                //if we do not find it try to check the identifier mapper
                if (associatedClass.getIdentifierMapper() == null)
                    return null;
                StringTokenizer st = new StringTokenizer(propertyName, ".", false);
                while (st.hasMoreElements()) {
                    String element = (String) st.nextElement();
                    if (property == null) {
                        property = associatedClass.getIdentifierMapper().getProperty(element);
                    } else {
                        if (!property.isComposite())
                            return null;
                        property = ((Component) property.getValue()).getProperty(element);
                    }
                }
            } catch (MappingException ee) {
                return null;
            }
        }
        return property;
    }
}