org.hibernate.cfg.annotations.reflection.JPAOverridenAnnotationReader.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.cfg.annotations.reflection.JPAOverridenAnnotationReader.java

Source

// $Id: JPAOverridenAnnotationReader.java 18522 2010-01-12 20:14:31Z hardy.ferentschik $
/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Middleware LLC.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * 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 Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.hibernate.cfg.annotations.reflection;

import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AssociationOverride;
import javax.persistence.AssociationOverrides;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ColumnResult;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.EntityResult;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.ExcludeDefaultListeners;
import javax.persistence.ExcludeSuperclassListeners;
import javax.persistence.FetchType;
import javax.persistence.FieldResult;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.MapKey;
import javax.persistence.MappedSuperclass;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.OrderBy;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.PrimaryKeyJoinColumns;
import javax.persistence.QueryHint;
import javax.persistence.SecondaryTable;
import javax.persistence.SecondaryTables;
import javax.persistence.SequenceGenerator;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.SqlResultSetMappings;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;
import javax.persistence.ElementCollection;

import org.dom4j.Attribute;
import org.dom4j.Element;
import org.hibernate.AnnotationException;
import org.hibernate.annotations.CollectionOfElements;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.common.annotationfactory.AnnotationDescriptor;
import org.hibernate.annotations.common.annotationfactory.AnnotationFactory;
import org.hibernate.annotations.common.reflection.AnnotationReader;
import org.hibernate.annotations.common.reflection.Filter;
import org.hibernate.annotations.common.reflection.ReflectionUtil;
import org.hibernate.util.ReflectHelper;
import org.hibernate.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Encapsulates the overriding of Java annotations from an EJB 3.0 descriptor.
 *
 * @author Paolo Perrotta
 * @author Davide Marchignoli
 * @author Emmanuel Bernard
 * @author Hardy Ferentschik
 */
@SuppressWarnings("unchecked")
public class JPAOverridenAnnotationReader implements AnnotationReader {
    private Logger log = LoggerFactory.getLogger(JPAOverridenAnnotationReader.class);
    private static final Map<Class, String> annotationToXml;
    private static final String SCHEMA_VALIDATION = "Activate schema validation for more information";
    private static final Filter FILTER = new Filter() {
        public boolean returnStatic() {
            return false;
        }

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

    static {
        annotationToXml = new HashMap<Class, String>();
        annotationToXml.put(Entity.class, "entity");
        annotationToXml.put(MappedSuperclass.class, "mapped-superclass");
        annotationToXml.put(Embeddable.class, "embeddable");
        annotationToXml.put(Table.class, "table");
        annotationToXml.put(SecondaryTable.class, "secondary-table");
        annotationToXml.put(SecondaryTables.class, "secondary-table");
        annotationToXml.put(PrimaryKeyJoinColumn.class, "primary-key-join-column");
        annotationToXml.put(PrimaryKeyJoinColumns.class, "primary-key-join-column");
        annotationToXml.put(IdClass.class, "id-class");
        annotationToXml.put(Inheritance.class, "inheritance");
        annotationToXml.put(DiscriminatorValue.class, "discriminator-value");
        annotationToXml.put(DiscriminatorColumn.class, "discriminator-column");
        annotationToXml.put(SequenceGenerator.class, "sequence-generator");
        annotationToXml.put(TableGenerator.class, "table-generator");
        annotationToXml.put(NamedQuery.class, "named-query");
        annotationToXml.put(NamedQueries.class, "named-query");
        annotationToXml.put(NamedNativeQuery.class, "named-native-query");
        annotationToXml.put(NamedNativeQueries.class, "named-native-query");
        annotationToXml.put(SqlResultSetMapping.class, "sql-result-set-mapping");
        annotationToXml.put(SqlResultSetMappings.class, "sql-result-set-mapping");
        annotationToXml.put(ExcludeDefaultListeners.class, "exclude-default-listeners");
        annotationToXml.put(ExcludeSuperclassListeners.class, "exclude-superclass-listeners");
        annotationToXml.put(AccessType.class, "access");
        annotationToXml.put(AttributeOverride.class, "attribute-override");
        annotationToXml.put(AttributeOverrides.class, "attribute-override");
        annotationToXml.put(AttributeOverride.class, "association-override");
        annotationToXml.put(AttributeOverrides.class, "association-override");
        annotationToXml.put(Id.class, "id");
        annotationToXml.put(EmbeddedId.class, "embedded-id");
        annotationToXml.put(GeneratedValue.class, "generated-value");
        annotationToXml.put(Column.class, "column");
        annotationToXml.put(Columns.class, "column");
        annotationToXml.put(Temporal.class, "temporal");
        annotationToXml.put(Lob.class, "lob");
        annotationToXml.put(Enumerated.class, "enumerated");
        annotationToXml.put(Version.class, "version");
        annotationToXml.put(Transient.class, "transient");
        annotationToXml.put(Basic.class, "basic");
        annotationToXml.put(Embedded.class, "embedded");
        annotationToXml.put(ManyToOne.class, "many-to-one");
        annotationToXml.put(OneToOne.class, "one-to-one");
        annotationToXml.put(OneToMany.class, "one-to-many");
        annotationToXml.put(ManyToMany.class, "many-to-many");
        annotationToXml.put(JoinTable.class, "join-table");
        annotationToXml.put(JoinColumn.class, "join-column");
        annotationToXml.put(JoinColumns.class, "join-column");
        annotationToXml.put(MapKey.class, "map-key");
        annotationToXml.put(OrderBy.class, "order-by");
        annotationToXml.put(EntityListeners.class, "entity-listeners");
        annotationToXml.put(PrePersist.class, "pre-persist");
        annotationToXml.put(PreRemove.class, "pre-remove");
        annotationToXml.put(PreUpdate.class, "pre-update");
        annotationToXml.put(PostPersist.class, "post-persist");
        annotationToXml.put(PostRemove.class, "post-remove");
        annotationToXml.put(PostUpdate.class, "post-update");
        annotationToXml.put(PostLoad.class, "post-load");
    }

    private XMLContext xmlContext;
    private String className;
    private String propertyName;
    private PropertyType propertyType;
    private transient Annotation[] annotations;
    private transient Map<Class, Annotation> annotationsMap;
    private static final String WORD_SEPARATOR = "-";
    private transient List<Element> elementsForProperty;
    private AccessibleObject mirroredAttribute;
    private final AnnotatedElement element;

    private enum PropertyType {
        PROPERTY, FIELD, METHOD
    }

    public JPAOverridenAnnotationReader(AnnotatedElement el, XMLContext xmlContext) {
        this.element = el;
        this.xmlContext = xmlContext;
        if (el instanceof Class) {
            Class clazz = (Class) el;
            className = clazz.getName();
        } else if (el instanceof Field) {
            Field field = (Field) el;
            className = field.getDeclaringClass().getName();
            propertyName = field.getName();
            propertyType = PropertyType.FIELD;
            String expectedGetter = "get" + Character.toUpperCase(propertyName.charAt(0))
                    + propertyName.substring(1);
            try {
                mirroredAttribute = field.getDeclaringClass().getDeclaredMethod(expectedGetter);
            } catch (NoSuchMethodException e) {
                //no method
            }
        } else if (el instanceof Method) {
            Method method = (Method) el;
            className = method.getDeclaringClass().getName();
            propertyName = method.getName();
            if (ReflectionUtil.isProperty(method, null, //this is yukky!! we'd rather get the TypeEnvironment()
                    FILTER)) {
                if (propertyName.startsWith("get")) {
                    propertyName = Introspector.decapitalize(propertyName.substring("get".length()));
                } else if (propertyName.startsWith("is")) {
                    propertyName = Introspector.decapitalize(propertyName.substring("is".length()));
                } else {
                    throw new RuntimeException("Method " + propertyName + " is not a property getter");
                }
                propertyType = PropertyType.PROPERTY;
                try {
                    mirroredAttribute = method.getDeclaringClass().getDeclaredField(propertyName);
                } catch (NoSuchFieldException e) {
                    //no method
                }
            } else {
                propertyType = PropertyType.METHOD;
            }
        } else {
            className = null;
            propertyName = null;
        }
    }

    public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
        initAnnotations();
        return (T) annotationsMap.get(annotationType);
    }

    public <T extends Annotation> boolean isAnnotationPresent(Class<T> annotationType) {
        initAnnotations();
        return (T) annotationsMap.get(annotationType) != null;
    }

    public Annotation[] getAnnotations() {
        initAnnotations();
        return annotations;
    }

    /*
     * The idea is to create annotation proxies for the xml configuration elements. Using this proxy annotations together
     * with the {@code JPAMetadataprovider} allows to handle xml configuration the same way as annotation configuration.
     */
    private void initAnnotations() {
        if (annotations == null) {
            XMLContext.Default defaults = xmlContext.getDefault(className);
            if (className != null && propertyName == null) {
                //is a class
                Element tree = xmlContext.getXMLTree(className);
                Annotation[] annotations = getJavaAnnotations();
                List<Annotation> annotationList = new ArrayList<Annotation>(annotations.length + 5);
                annotationsMap = new HashMap<Class, Annotation>(annotations.length + 5);
                for (Annotation annotation : annotations) {
                    if (!annotationToXml.containsKey(annotation.annotationType())) {
                        //unknown annotations are left over
                        annotationList.add(annotation);
                    }
                }
                addIfNotNull(annotationList, getEntity(tree, defaults));
                addIfNotNull(annotationList, getMappedSuperclass(tree, defaults));
                addIfNotNull(annotationList, getEmbeddable(tree, defaults));
                addIfNotNull(annotationList, getTable(tree, defaults));
                addIfNotNull(annotationList, getSecondaryTables(tree, defaults));
                addIfNotNull(annotationList, getPrimaryKeyJoinColumns(tree, defaults));
                addIfNotNull(annotationList, getIdClass(tree, defaults));
                addIfNotNull(annotationList, getInheritance(tree, defaults));
                addIfNotNull(annotationList, getDiscriminatorValue(tree, defaults));
                addIfNotNull(annotationList, getDiscriminatorColumn(tree, defaults));
                addIfNotNull(annotationList, getSequenceGenerator(tree, defaults));
                addIfNotNull(annotationList, getTableGenerator(tree, defaults));
                addIfNotNull(annotationList, getNamedQueries(tree, defaults));
                addIfNotNull(annotationList, getNamedNativeQueries(tree, defaults));
                addIfNotNull(annotationList, getSqlResultSetMappings(tree, defaults));
                addIfNotNull(annotationList, getExcludeDefaultListeners(tree, defaults));
                addIfNotNull(annotationList, getExcludeSuperclassListeners(tree, defaults));
                addIfNotNull(annotationList, getAccessType(tree, defaults));
                addIfNotNull(annotationList, getAttributeOverrides(tree, defaults));
                addIfNotNull(annotationList, getAssociationOverrides(tree, defaults));
                addIfNotNull(annotationList, getEntityListeners(tree, defaults));
                //FIXME use annotationsMap rather than annotationList this will be faster since the annotation type is usually known at put() time
                this.annotations = annotationList.toArray(new Annotation[annotationList.size()]);
                for (Annotation ann : this.annotations) {
                    annotationsMap.put(ann.annotationType(), ann);
                }
                checkForOrphanProperties(tree);
            } else if (className != null) { //&& propertyName != null ) { //always true but less confusing
                Element tree = xmlContext.getXMLTree(className);
                Annotation[] annotations = getJavaAnnotations();
                List<Annotation> annotationList = new ArrayList<Annotation>(annotations.length + 5);
                annotationsMap = new HashMap<Class, Annotation>(annotations.length + 5);
                for (Annotation annotation : annotations) {
                    if (!annotationToXml.containsKey(annotation.annotationType())) {
                        //unknown annotations are left over
                        annotationList.add(annotation);
                    }
                }
                preCalculateElementsForProperty(tree);
                Transient transientAnn = getTransient(defaults);
                if (transientAnn != null) {
                    annotationList.add(transientAnn);
                } else {
                    if (defaults.canUseJavaAnnotations()) {
                        Annotation annotation = getJavaAnnotation(Access.class);
                        addIfNotNull(annotationList, annotation);
                    }
                    getId(annotationList, defaults);
                    getEmbeddedId(annotationList, defaults);
                    getEmbedded(annotationList, defaults);
                    getBasic(annotationList, defaults);
                    getVersion(annotationList, defaults);
                    getAssociation(ManyToOne.class, annotationList, defaults);
                    getAssociation(OneToOne.class, annotationList, defaults);
                    getAssociation(OneToMany.class, annotationList, defaults);
                    getAssociation(ManyToMany.class, annotationList, defaults);
                    getElementCollection(annotationList, defaults);
                    addIfNotNull(annotationList, getSequenceGenerator(elementsForProperty, defaults));
                    addIfNotNull(annotationList, getTableGenerator(elementsForProperty, defaults));
                    addIfNotNull(annotationList, getAttributeOverrides(elementsForProperty, defaults));

                }
                processEventAnnotations(annotationList, defaults);
                //FIXME use annotationsMap rather than annotationList this will be faster since the annotation type is usually known at put() time
                this.annotations = annotationList.toArray(new Annotation[annotationList.size()]);
                for (Annotation ann : this.annotations) {
                    annotationsMap.put(ann.annotationType(), ann);
                }
            } else {
                this.annotations = getJavaAnnotations();
                annotationsMap = new HashMap<Class, Annotation>(annotations.length + 5);
                for (Annotation ann : this.annotations) {
                    annotationsMap.put(ann.annotationType(), ann);
                }
            }
        }
    }

    private void checkForOrphanProperties(Element tree) {
        Class clazz;
        try {
            clazz = ReflectHelper.classForName(className, this.getClass());
        } catch (ClassNotFoundException e) {
            return; //a primitive type most likely
        }
        Element element = tree != null ? tree.element("attributes") : null;
        //put entity.attributes elements
        if (element != null) {
            //precompute the list of properties
            //TODO is it really useful...
            Set<String> properties = new HashSet<String>();
            for (Field field : clazz.getFields()) {
                properties.add(field.getName());
            }
            for (Method method : clazz.getMethods()) {
                String name = method.getName();
                if (name.startsWith("get")) {
                    properties.add(Introspector.decapitalize(name.substring("get".length())));
                } else if (name.startsWith("is")) {
                    properties.add(Introspector.decapitalize(name.substring("is".length())));
                }
            }
            for (Element subelement : (List<Element>) element.elements()) {
                String propertyName = subelement.attributeValue("name");
                if (!properties.contains(propertyName)) {
                    log.warn(
                            "Property {} not found in class"
                                    + " but described in <mapping-file/> (possible typo error)",
                            StringHelper.qualify(className, propertyName));
                }
            }
        }
    }

    /**
     * Adds {@code annotation} to the list (only if it's not null) and then returns it.
     *
     * @param annotationList The list of annotations.
     * @param annotation The annotation to add to the list.
     *
     * @return The annotation which was added to the list or {@code null}.
     */
    private Annotation addIfNotNull(List<Annotation> annotationList, Annotation annotation) {
        if (annotation != null) {
            annotationList.add(annotation);
        }
        return annotation;
    }

    //TODO mutualize the next 2 methods
    private Annotation getTableGenerator(List<Element> elementsForProperty, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            Element subelement = element != null ? element.element(annotationToXml.get(TableGenerator.class))
                    : null;
            if (subelement != null) {
                return buildTableGeneratorAnnotation(subelement, defaults);
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            return getJavaAnnotation(TableGenerator.class);
        } else {
            return null;
        }
    }

    private Annotation getSequenceGenerator(List<Element> elementsForProperty, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            Element subelement = element != null ? element.element(annotationToXml.get(SequenceGenerator.class))
                    : null;
            if (subelement != null) {
                return buildSequenceGeneratorAnnotation(subelement);
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            return getJavaAnnotation(SequenceGenerator.class);
        } else {
            return null;
        }
    }

    private void processEventAnnotations(List<Annotation> annotationList, XMLContext.Default defaults) {
        boolean eventElement = false;
        for (Element element : elementsForProperty) {
            String elementName = element.getName();
            if ("pre-persist".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PrePersist.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            } else if ("pre-remove".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PreRemove.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            } else if ("pre-update".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PreUpdate.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            } else if ("post-persist".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PostPersist.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            } else if ("post-remove".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PostRemove.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            } else if ("post-update".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PostUpdate.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            } else if ("post-load".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PostLoad.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            }
        }
        if (!eventElement && defaults.canUseJavaAnnotations()) {
            Annotation ann = getJavaAnnotation(PrePersist.class);
            addIfNotNull(annotationList, ann);
            ann = getJavaAnnotation(PreRemove.class);
            addIfNotNull(annotationList, ann);
            ann = getJavaAnnotation(PreUpdate.class);
            addIfNotNull(annotationList, ann);
            ann = getJavaAnnotation(PostPersist.class);
            addIfNotNull(annotationList, ann);
            ann = getJavaAnnotation(PostRemove.class);
            addIfNotNull(annotationList, ann);
            ann = getJavaAnnotation(PostUpdate.class);
            addIfNotNull(annotationList, ann);
            ann = getJavaAnnotation(PostLoad.class);
            addIfNotNull(annotationList, ann);
        }
    }

    private EntityListeners getEntityListeners(Element tree, XMLContext.Default defaults) {
        Element element = tree != null ? tree.element("entity-listeners") : null;
        if (element != null) {
            List<Class> entityListenerClasses = new ArrayList<Class>();
            for (Element subelement : (List<Element>) element.elements("entity-listener")) {
                String className = subelement.attributeValue("class");
                try {
                    entityListenerClasses.add(ReflectHelper
                            .classForName(XMLContext.buildSafeClassName(className, defaults), this.getClass()));
                } catch (ClassNotFoundException e) {
                    throw new AnnotationException("Unable to find " + element.getPath() + ".class: " + className,
                            e);
                }
            }
            AnnotationDescriptor ad = new AnnotationDescriptor(EntityListeners.class);
            ad.setValue("value", entityListenerClasses.toArray(new Class[entityListenerClasses.size()]));
            return AnnotationFactory.create(ad);
        } else if (defaults.canUseJavaAnnotations()) {
            return getJavaAnnotation(EntityListeners.class);
        } else {
            return null;
        }
    }

    private JoinTable overridesDefaultsInJoinTable(Annotation annotation, XMLContext.Default defaults) {
        //no element but might have some default or some annotation
        boolean defaultToJoinTable = !(isJavaAnnotationPresent(JoinColumn.class)
                || isJavaAnnotationPresent(JoinColumns.class));
        final Class<? extends Annotation> annotationClass = annotation.annotationType();
        defaultToJoinTable = defaultToJoinTable && ((annotationClass == ManyToMany.class
                && StringHelper.isEmpty(((ManyToMany) annotation).mappedBy()))
                || (annotationClass == OneToMany.class && StringHelper.isEmpty(((OneToMany) annotation).mappedBy()))
                || (annotationClass == CollectionOfElements.class) //legacy Hibernate
                || (annotationClass == ElementCollection.class));
        final Class<JoinTable> annotationType = JoinTable.class;
        if (defaultToJoinTable && (StringHelper.isNotEmpty(defaults.getCatalog())
                || StringHelper.isNotEmpty(defaults.getSchema()))) {
            AnnotationDescriptor ad = new AnnotationDescriptor(annotationType);
            if (defaults.canUseJavaAnnotations()) {
                JoinTable table = getJavaAnnotation(annotationType);
                if (table != null) {
                    ad.setValue("name", table.name());
                    ad.setValue("schema", table.schema());
                    ad.setValue("catalog", table.catalog());
                    ad.setValue("uniqueConstraints", table.uniqueConstraints());
                    ad.setValue("joinColumns", table.joinColumns());
                    ad.setValue("inverseJoinColumns", table.inverseJoinColumns());
                }
            }
            if (StringHelper.isEmpty((String) ad.valueOf("schema"))
                    && StringHelper.isNotEmpty(defaults.getSchema())) {
                ad.setValue("schema", defaults.getSchema());
            }
            if (StringHelper.isEmpty((String) ad.valueOf("catalog"))
                    && StringHelper.isNotEmpty(defaults.getCatalog())) {
                ad.setValue("catalog", defaults.getCatalog());
            }
            return AnnotationFactory.create(ad);
        } else if (defaults.canUseJavaAnnotations()) {
            return getJavaAnnotation(annotationType);
        } else {
            return null;
        }
    }

    /*
     * no partial overriding possible
     */
    private void getJoinTable(List<Annotation> annotationList, Element tree, XMLContext.Default defaults) {
        Element subelement = tree == null ? null : tree.element("join-table");
        final Class<JoinTable> annotationType = JoinTable.class;
        if (subelement != null) {
            //ignore java annotation, an element is defined
            AnnotationDescriptor annotation = new AnnotationDescriptor(annotationType);
            copyStringAttribute(annotation, subelement, "name", false);
            copyStringAttribute(annotation, subelement, "catalog", false);
            if (StringHelper.isNotEmpty(defaults.getCatalog())
                    && StringHelper.isEmpty((String) annotation.valueOf("catalog"))) {
                annotation.setValue("catalog", defaults.getCatalog());
            }
            copyStringAttribute(annotation, subelement, "schema", false);
            if (StringHelper.isNotEmpty(defaults.getSchema())
                    && StringHelper.isEmpty((String) annotation.valueOf("schema"))) {
                annotation.setValue("schema", defaults.getSchema());
            }
            buildUniqueConstraints(annotation, subelement);
            annotation.setValue("joinColumns", getJoinColumns(subelement, false));
            annotation.setValue("inverseJoinColumns", getJoinColumns(subelement, true));
            annotationList.add(AnnotationFactory.create(annotation));
        }
    }

    private void getAssociation(Class<? extends Annotation> annotationType, List<Annotation> annotationList,
            XMLContext.Default defaults) {
        String xmlName = annotationToXml.get(annotationType);
        for (Element element : elementsForProperty) {
            if (xmlName.equals(element.getName())) {
                AnnotationDescriptor ad = new AnnotationDescriptor(annotationType);
                addTargetClass(element, ad, "target-entity", defaults);
                getFetchType(ad, element);
                getCascades(ad, element, defaults);
                getJoinTable(annotationList, element, defaults);
                buildJoinColumns(annotationList, element);
                Annotation annotation = getPrimaryKeyJoinColumns(element, defaults);
                addIfNotNull(annotationList, annotation);
                copyBooleanAttribute(ad, element, "optional");
                copyStringAttribute(ad, element, "mapped-by", false);
                getOrderBy(annotationList, element);
                getMapKey(annotationList, element);
                annotationList.add(AnnotationFactory.create(ad));
                getAccessType(annotationList, element);
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            Annotation annotation = getJavaAnnotation(annotationType);
            if (annotation != null) {
                annotationList.add(annotation);
                annotation = overridesDefaultsInJoinTable(annotation, defaults);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(JoinColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(JoinColumns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(PrimaryKeyJoinColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(PrimaryKeyJoinColumns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(MapKey.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(OrderBy.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AttributeOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AttributeOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AssociationOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AssociationOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Lob.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Enumerated.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Temporal.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Column.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Columns.class);
                addIfNotNull(annotationList, annotation);
            } else if (isJavaAnnotationPresent(ElementCollection.class)) { //JPA2
                annotation = overridesDefaultsInJoinTable(getJavaAnnotation(ElementCollection.class), defaults);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(JoinColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(JoinColumns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(PrimaryKeyJoinColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(PrimaryKeyJoinColumns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(MapKey.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(OrderBy.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AttributeOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AttributeOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AssociationOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AssociationOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Lob.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Enumerated.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Temporal.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Column.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Columns.class);
                addIfNotNull(annotationList, annotation);
            } else if (isJavaAnnotationPresent(CollectionOfElements.class)) { //legacy Hibernate
                annotation = overridesDefaultsInJoinTable(getJavaAnnotation(CollectionOfElements.class), defaults);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(JoinColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(JoinColumns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(PrimaryKeyJoinColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(PrimaryKeyJoinColumns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(MapKey.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(OrderBy.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AttributeOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AttributeOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AssociationOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AssociationOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Lob.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Enumerated.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Temporal.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Column.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Columns.class);
                addIfNotNull(annotationList, annotation);
            }
        }
    }

    private void addTargetClass(Element element, AnnotationDescriptor ad, String nodeName,
            XMLContext.Default defaults) {
        String className = element.attributeValue(nodeName);
        if (className != null) {
            Class clazz;
            try {
                clazz = ReflectHelper.classForName(XMLContext.buildSafeClassName(className, defaults),
                        this.getClass());
            } catch (ClassNotFoundException e) {
                throw new AnnotationException(
                        "Unable to find " + element.getPath() + " " + nodeName + ": " + className, e);
            }
            ad.setValue(getJavaAttributeNameFromXMLOne(nodeName), clazz);
        }
    }

    // TODO: Complete parsing of all element-collection related xml
    private void getElementCollection(List<Annotation> annotationList, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("element-collection".equals(element.getName())) {
                AnnotationDescriptor ad = new AnnotationDescriptor(ElementCollection.class);
                addTargetClass(element, ad, "target-class", defaults);
                annotationList.add(AnnotationFactory.create(ad));

                getAccessType(annotationList, element);
            }
        }
    }

    private void getOrderBy(List<Annotation> annotationList, Element element) {
        Element subelement = element != null ? element.element("order-by") : null;
        if (subelement != null) {
            String orderByString = subelement.getTextTrim();
            AnnotationDescriptor ad = new AnnotationDescriptor(OrderBy.class);
            if (StringHelper.isNotEmpty(orderByString))
                ad.setValue("value", orderByString);
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private void getMapKey(List<Annotation> annotationList, Element element) {
        Element subelement = element != null ? element.element("map-key") : null;
        if (subelement != null) {
            String mapKeyString = subelement.attributeValue("name");
            AnnotationDescriptor ad = new AnnotationDescriptor(MapKey.class);
            if (StringHelper.isNotEmpty(mapKeyString))
                ad.setValue("name", mapKeyString);
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private void buildJoinColumns(List<Annotation> annotationList, Element element) {
        JoinColumn[] joinColumns = getJoinColumns(element, false);
        if (joinColumns.length > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(JoinColumns.class);
            ad.setValue("value", joinColumns);
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private void getCascades(AnnotationDescriptor ad, Element element, XMLContext.Default defaults) {
        List<Element> elements = element != null ? element.elements("cascade") : new ArrayList<Element>(0);
        List<CascadeType> cascades = new ArrayList<CascadeType>();
        for (Element subelement : elements) {
            if (subelement.element("cascade-all") != null)
                cascades.add(CascadeType.ALL);
            if (subelement.element("cascade-persist") != null)
                cascades.add(CascadeType.PERSIST);
            if (subelement.element("cascade-merge") != null)
                cascades.add(CascadeType.MERGE);
            if (subelement.element("cascade-remove") != null)
                cascades.add(CascadeType.REMOVE);
            if (subelement.element("cascade-refresh") != null)
                cascades.add(CascadeType.REFRESH);
            if (subelement.element("cascade-detach") != null)
                cascades.add(CascadeType.DETACH);
        }
        if (Boolean.TRUE.equals(defaults.getCascadePersist()) && !cascades.contains(CascadeType.ALL)
                && !cascades.contains(CascadeType.PERSIST)) {
            cascades.add(CascadeType.PERSIST);
        }
        if (cascades.size() > 0) {
            ad.setValue("cascade", cascades.toArray(new CascadeType[cascades.size()]));
        }
    }

    private void getEmbedded(List<Annotation> annotationList, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("embedded".equals(element.getName())) {
                AnnotationDescriptor ad = new AnnotationDescriptor(Embedded.class);
                annotationList.add(AnnotationFactory.create(ad));
                getAccessType(annotationList, element);
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            Annotation annotation = getJavaAnnotation(Embedded.class);
            if (annotation != null) {
                annotationList.add(annotation);
                annotation = getJavaAnnotation(AttributeOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AttributeOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AssociationOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AssociationOverrides.class);
                addIfNotNull(annotationList, annotation);
            }
        }
    }

    private Transient getTransient(XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("transient".equals(element.getName())) {
                AnnotationDescriptor ad = new AnnotationDescriptor(Transient.class);
                return AnnotationFactory.create(ad);
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            return getJavaAnnotation(Transient.class);
        } else {
            return null;
        }
    }

    private void getVersion(List<Annotation> annotationList, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("version".equals(element.getName())) {
                Annotation annotation = buildColumns(element);
                addIfNotNull(annotationList, annotation);
                getTemporal(annotationList, element);
                AnnotationDescriptor basic = new AnnotationDescriptor(Version.class);
                annotationList.add(AnnotationFactory.create(basic));
                getAccessType(annotationList, element);
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            //we have nothing, so Java annotations might occurs
            Annotation annotation = getJavaAnnotation(Version.class);
            if (annotation != null) {
                annotationList.add(annotation);
                annotation = getJavaAnnotation(Column.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Columns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Temporal.class);
                addIfNotNull(annotationList, annotation);
            }
        }
    }

    private void getBasic(List<Annotation> annotationList, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("basic".equals(element.getName())) {
                Annotation annotation = buildColumns(element);
                addIfNotNull(annotationList, annotation);
                getAccessType(annotationList, element);
                getTemporal(annotationList, element);
                getLob(annotationList, element);
                getEnumerated(annotationList, element);
                AnnotationDescriptor basic = new AnnotationDescriptor(Basic.class);
                getFetchType(basic, element);
                copyBooleanAttribute(basic, element, "optional");
                annotationList.add(AnnotationFactory.create(basic));
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            //no annotation presence constraint, basic is the default
            Annotation annotation = getJavaAnnotation(Basic.class);
            addIfNotNull(annotationList, annotation);
            annotation = getJavaAnnotation(Lob.class);
            addIfNotNull(annotationList, annotation);
            annotation = getJavaAnnotation(Enumerated.class);
            addIfNotNull(annotationList, annotation);
            annotation = getJavaAnnotation(Temporal.class);
            addIfNotNull(annotationList, annotation);
            annotation = getJavaAnnotation(Column.class);
            addIfNotNull(annotationList, annotation);
            annotation = getJavaAnnotation(Columns.class);
            addIfNotNull(annotationList, annotation);
            annotation = getJavaAnnotation(AttributeOverride.class);
            addIfNotNull(annotationList, annotation);
            annotation = getJavaAnnotation(AttributeOverrides.class);
            addIfNotNull(annotationList, annotation);
            annotation = getJavaAnnotation(AssociationOverride.class);
            addIfNotNull(annotationList, annotation);
            annotation = getJavaAnnotation(AssociationOverrides.class);
            addIfNotNull(annotationList, annotation);
        }
    }

    private void getEnumerated(List<Annotation> annotationList, Element element) {
        Element subElement = element != null ? element.element("enumerated") : null;
        if (subElement != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(Enumerated.class);
            String enumerated = subElement.getTextTrim();
            if ("ORDINAL".equalsIgnoreCase(enumerated)) {
                ad.setValue("value", EnumType.ORDINAL);
            } else if ("STRING".equalsIgnoreCase(enumerated)) {
                ad.setValue("value", EnumType.STRING);
            } else if (StringHelper.isNotEmpty(enumerated)) {
                throw new AnnotationException("Unknown EnumType: " + enumerated + ". " + SCHEMA_VALIDATION);
            }
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private void getLob(List<Annotation> annotationList, Element element) {
        Element subElement = element != null ? element.element("lob") : null;
        if (subElement != null) {
            annotationList.add(AnnotationFactory.create(new AnnotationDescriptor(Lob.class)));
        }
    }

    private void getFetchType(AnnotationDescriptor descriptor, Element element) {
        String fetchString = element != null ? element.attributeValue("fetch") : null;
        if (fetchString != null) {
            if ("eager".equalsIgnoreCase(fetchString)) {
                descriptor.setValue("fetch", FetchType.EAGER);
            } else if ("lazy".equalsIgnoreCase(fetchString)) {
                descriptor.setValue("fetch", FetchType.LAZY);
            }
        }
    }

    private void getEmbeddedId(List<Annotation> annotationList, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("embedded-id".equals(element.getName())) {
                if (isProcessingId(defaults)) {
                    Annotation annotation = getAttributeOverrides(element, defaults);
                    addIfNotNull(annotationList, annotation);
                    annotation = getAssociationOverrides(element, defaults);
                    addIfNotNull(annotationList, annotation);
                    AnnotationDescriptor ad = new AnnotationDescriptor(EmbeddedId.class);
                    annotationList.add(AnnotationFactory.create(ad));
                    getAccessType(annotationList, element);
                }
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            Annotation annotation = getJavaAnnotation(EmbeddedId.class);
            if (annotation != null) {
                annotationList.add(annotation);
                annotation = getJavaAnnotation(Column.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Columns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(GeneratedValue.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Temporal.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(TableGenerator.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(SequenceGenerator.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AttributeOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AttributeOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AssociationOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AssociationOverrides.class);
                addIfNotNull(annotationList, annotation);
            }
        }
    }

    private void preCalculateElementsForProperty(Element tree) {
        elementsForProperty = new ArrayList<Element>();
        Element element = tree != null ? tree.element("attributes") : null;
        //put entity.attributes elements
        if (element != null) {
            for (Element subelement : (List<Element>) element.elements()) {
                if (propertyName.equals(subelement.attributeValue("name"))) {
                    elementsForProperty.add(subelement);
                }
            }
        }
        //add pre-* etc from entity and pure entity listener classes
        if (tree != null) {
            for (Element subelement : (List<Element>) tree.elements()) {
                if (propertyName.equals(subelement.attributeValue("method-name"))) {
                    elementsForProperty.add(subelement);
                }
            }
        }
    }

    private void getId(List<Annotation> annotationList, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("id".equals(element.getName())) {
                boolean processId = isProcessingId(defaults);
                if (processId) {
                    Annotation annotation = buildColumns(element);
                    addIfNotNull(annotationList, annotation);
                    annotation = buildGeneratedValue(element);
                    addIfNotNull(annotationList, annotation);
                    getTemporal(annotationList, element);
                    //FIXME: fix the priority of xml over java for generator names
                    annotation = getTableGenerator(element, defaults);
                    addIfNotNull(annotationList, annotation);
                    annotation = getSequenceGenerator(element, defaults);
                    addIfNotNull(annotationList, annotation);
                    AnnotationDescriptor id = new AnnotationDescriptor(Id.class);
                    annotationList.add(AnnotationFactory.create(id));
                    getAccessType(annotationList, element);
                }
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            Annotation annotation = getJavaAnnotation(Id.class);
            if (annotation != null) {
                annotationList.add(annotation);
                annotation = getJavaAnnotation(Column.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Columns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(GeneratedValue.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(Temporal.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(TableGenerator.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(SequenceGenerator.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AttributeOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AttributeOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AssociationOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getJavaAnnotation(AssociationOverrides.class);
                addIfNotNull(annotationList, annotation);
            }
        }
    }

    private boolean isProcessingId(XMLContext.Default defaults) {
        boolean isExplicit = defaults.getAccess() != null;
        boolean correctAccess = (PropertyType.PROPERTY.equals(propertyType)
                && AccessType.PROPERTY.equals(defaults.getAccess()))
                || (PropertyType.FIELD.equals(propertyType) && AccessType.FIELD.equals(defaults.getAccess()));
        boolean hasId = defaults.canUseJavaAnnotations()
                && (isJavaAnnotationPresent(Id.class) || isJavaAnnotationPresent(EmbeddedId.class));
        //if ( properAccessOnMetadataComplete || properOverridingOnMetadataNonComplete ) {
        boolean mirrorAttributeIsId = defaults.canUseJavaAnnotations()
                && (mirroredAttribute != null && (mirroredAttribute.isAnnotationPresent(Id.class)
                        || mirroredAttribute.isAnnotationPresent(EmbeddedId.class)));
        boolean propertyIsDefault = PropertyType.PROPERTY.equals(propertyType) && !mirrorAttributeIsId;
        return correctAccess || (!isExplicit && hasId) || (!isExplicit && propertyIsDefault);
    }

    private Columns buildColumns(Element element) {
        List<Element> subelements = element.elements("column");
        List<Column> columns = new ArrayList<Column>(subelements.size());
        for (Element subelement : subelements) {
            columns.add(getColumn(subelement, false, element));
        }
        if (columns.size() > 0) {
            AnnotationDescriptor columnsDescr = new AnnotationDescriptor(Columns.class);
            columnsDescr.setValue("columns", columns.toArray(new Column[columns.size()]));
            return AnnotationFactory.create(columnsDescr);
        } else {
            return null;
        }
    }

    private GeneratedValue buildGeneratedValue(Element element) {
        Element subElement = element != null ? element.element("generated-value") : null;
        if (subElement != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(GeneratedValue.class);
            String strategy = subElement.attributeValue("strategy");
            if ("TABLE".equalsIgnoreCase(strategy)) {
                ad.setValue("strategy", GenerationType.TABLE);
            } else if ("SEQUENCE".equalsIgnoreCase(strategy)) {
                ad.setValue("strategy", GenerationType.SEQUENCE);
            } else if ("IDENTITY".equalsIgnoreCase(strategy)) {
                ad.setValue("strategy", GenerationType.IDENTITY);
            } else if ("AUTO".equalsIgnoreCase(strategy)) {
                ad.setValue("strategy", GenerationType.AUTO);
            } else if (StringHelper.isNotEmpty(strategy)) {
                throw new AnnotationException("Unknown GenerationType: " + strategy + ". " + SCHEMA_VALIDATION);
            }
            copyStringAttribute(ad, subElement, "generator", false);
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private void getTemporal(List<Annotation> annotationList, Element element) {
        Element subElement = element != null ? element.element("temporal") : null;
        if (subElement != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(Temporal.class);
            String temporal = subElement.getTextTrim();
            if ("DATE".equalsIgnoreCase(temporal)) {
                ad.setValue("value", TemporalType.DATE);
            } else if ("TIME".equalsIgnoreCase(temporal)) {
                ad.setValue("value", TemporalType.TIME);
            } else if ("TIMESTAMP".equalsIgnoreCase(temporal)) {
                ad.setValue("value", TemporalType.TIMESTAMP);
            } else if (StringHelper.isNotEmpty(temporal)) {
                throw new AnnotationException("Unknown TemporalType: " + temporal + ". " + SCHEMA_VALIDATION);
            }
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private void getAccessType(List<Annotation> annotationList, Element element) {
        if (element == null) {
            return;
        }
        String access = element.attributeValue("access");
        if (access != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(Access.class);
            AccessType type;
            try {
                type = AccessType.valueOf(access);
            } catch (IllegalArgumentException e) {
                throw new AnnotationException(access + " is not a valid access type. Check you xml confguration.");
            }

            if ((AccessType.PROPERTY.equals(type) && this.element instanceof Method)
                    || (AccessType.FIELD.equals(type) && this.element instanceof Field)) {
                return;
            }

            ad.setValue("value", type);
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private AssociationOverrides getAssociationOverrides(Element tree, XMLContext.Default defaults) {
        List<AssociationOverride> attributes = (List<AssociationOverride>) buildAssociationOverrides(tree);
        if (defaults.canUseJavaAnnotations()) {
            AssociationOverride annotation = getJavaAnnotation(AssociationOverride.class);
            addAssociationOverrideIfNeeded(annotation, attributes);
            AssociationOverrides annotations = getJavaAnnotation(AssociationOverrides.class);
            if (annotations != null) {
                for (AssociationOverride current : annotations.value()) {
                    addAssociationOverrideIfNeeded(current, attributes);
                }
            }
        }
        if (attributes.size() > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(AssociationOverrides.class);
            ad.setValue("value", attributes.toArray(new AssociationOverride[attributes.size()]));
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private List<AssociationOverride> buildAssociationOverrides(Element element) {
        List<Element> subelements = element == null ? null : element.elements("association-override");
        List<AssociationOverride> overrides = new ArrayList<AssociationOverride>();
        if (subelements != null && subelements.size() > 0) {
            for (Element current : subelements) {
                AnnotationDescriptor override = new AnnotationDescriptor(AssociationOverride.class);
                copyStringAttribute(override, current, "name", true);
                override.setValue("joinColumns", getJoinColumns(current, false));
                overrides.add((AssociationOverride) AnnotationFactory.create(override));
            }
        }
        return overrides;
    }

    private JoinColumn[] getJoinColumns(Element element, boolean isInverse) {
        List<Element> subelements = element != null
                ? element.elements(isInverse ? "inverse-join-column" : "join-column")
                : null;
        List<JoinColumn> joinColumns = new ArrayList<JoinColumn>();
        if (subelements != null) {
            for (Element subelement : subelements) {
                AnnotationDescriptor column = new AnnotationDescriptor(JoinColumn.class);
                copyStringAttribute(column, subelement, "name", false);
                copyStringAttribute(column, subelement, "referenced-column-name", false);
                copyBooleanAttribute(column, subelement, "unique");
                copyBooleanAttribute(column, subelement, "nullable");
                copyBooleanAttribute(column, subelement, "insertable");
                copyBooleanAttribute(column, subelement, "updatable");
                copyStringAttribute(column, subelement, "column-definition", false);
                copyStringAttribute(column, subelement, "table", false);
                joinColumns.add((JoinColumn) AnnotationFactory.create(column));
            }
        }
        return joinColumns.toArray(new JoinColumn[joinColumns.size()]);
    }

    private void addAssociationOverrideIfNeeded(AssociationOverride annotation,
            List<AssociationOverride> overrides) {
        if (annotation != null) {
            String overrideName = annotation.name();
            boolean present = false;
            for (AssociationOverride current : overrides) {
                if (current.name().equals(overrideName)) {
                    present = true;
                    break;
                }
            }
            if (!present)
                overrides.add(annotation);
        }
    }

    private AttributeOverrides getAttributeOverrides(Element tree, XMLContext.Default defaults) {
        List<AttributeOverride> attributes = buildAttributeOverrides(tree);
        return mergeAttributeOverrides(defaults, attributes);
    }

    private AttributeOverrides getAttributeOverrides(List<Element> elements, XMLContext.Default defaults) {
        List<AttributeOverride> attributes = new ArrayList<AttributeOverride>();
        for (Element element : elements) {
            attributes.addAll(buildAttributeOverrides(element));
        }
        return mergeAttributeOverrides(defaults, attributes);
    }

    private AttributeOverrides mergeAttributeOverrides(XMLContext.Default defaults,
            List<AttributeOverride> attributes) {
        if (defaults.canUseJavaAnnotations()) {
            AttributeOverride annotation = getJavaAnnotation(AttributeOverride.class);
            addAttributeOverrideIfNeeded(annotation, attributes);
            AttributeOverrides annotations = getJavaAnnotation(AttributeOverrides.class);
            if (annotations != null) {
                for (AttributeOverride current : annotations.value()) {
                    addAttributeOverrideIfNeeded(current, attributes);
                }
            }
        }
        if (attributes.size() > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(AttributeOverrides.class);
            ad.setValue("value", attributes.toArray(new AttributeOverride[attributes.size()]));
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private List<AttributeOverride> buildAttributeOverrides(Element element) {
        List<Element> subelements = element == null ? null : element.elements("attribute-override");
        return buildAttributeOverrides(subelements);
    }

    private List<AttributeOverride> buildAttributeOverrides(List<Element> subelements) {
        List<AttributeOverride> overrides = new ArrayList<AttributeOverride>();
        if (subelements != null && subelements.size() > 0) {
            for (Element current : subelements) {
                if (!current.getName().equals("attribute-override"))
                    continue;
                AnnotationDescriptor override = new AnnotationDescriptor(AttributeOverride.class);
                copyStringAttribute(override, current, "name", true);
                Element column = current.element("column");
                override.setValue("column", getColumn(column, true, current));
                overrides.add((AttributeOverride) AnnotationFactory.create(override));
            }
        }
        return overrides;
    }

    private Column getColumn(Element element, boolean isMandatory, Element current) {
        //Element subelement = element != null ? element.element( "column" ) : null;
        if (element != null) {
            AnnotationDescriptor column = new AnnotationDescriptor(Column.class);
            copyStringAttribute(column, element, "name", false);
            copyBooleanAttribute(column, element, "unique");
            copyBooleanAttribute(column, element, "nullable");
            copyBooleanAttribute(column, element, "insertable");
            copyBooleanAttribute(column, element, "updatable");
            copyStringAttribute(column, element, "column-definition", false);
            copyStringAttribute(column, element, "table", false);
            copyIntegerAttribute(column, element, "length");
            copyIntegerAttribute(column, element, "precision");
            copyIntegerAttribute(column, element, "scale");
            return (Column) AnnotationFactory.create(column);
        } else {
            if (isMandatory) {
                throw new AnnotationException(current.getPath() + ".column is mandatory. " + SCHEMA_VALIDATION);
            }
            return null;
        }
    }

    private void addAttributeOverrideIfNeeded(AttributeOverride annotation, List<AttributeOverride> overrides) {
        if (annotation != null) {
            String overrideName = annotation.name();
            boolean present = false;
            for (AttributeOverride current : overrides) {
                if (current.name().equals(overrideName)) {
                    present = true;
                    break;
                }
            }
            if (!present)
                overrides.add(annotation);
        }
    }

    private Access getAccessType(Element tree, XMLContext.Default defaults) {
        String access = tree == null ? null : tree.attributeValue("access");
        if (access != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(Access.class);
            AccessType type;
            try {
                type = AccessType.valueOf(access);
            } catch (IllegalArgumentException e) {
                throw new AnnotationException(access + " is not a valid access type. Check you xml confguration.");
            }
            ad.setValue("value", type);
            return AnnotationFactory.create(ad);
        } else if (defaults.canUseJavaAnnotations() && isJavaAnnotationPresent(Access.class)) {
            return getJavaAnnotation(Access.class);
        } else if (defaults.getAccess() != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(Access.class);
            ad.setValue("value", defaults.getAccess());
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private ExcludeSuperclassListeners getExcludeSuperclassListeners(Element tree, XMLContext.Default defaults) {
        return (ExcludeSuperclassListeners) getMarkerAnnotation(ExcludeSuperclassListeners.class, tree, defaults);
    }

    private ExcludeDefaultListeners getExcludeDefaultListeners(Element tree, XMLContext.Default defaults) {
        return (ExcludeDefaultListeners) getMarkerAnnotation(ExcludeDefaultListeners.class, tree, defaults);
    }

    private Annotation getMarkerAnnotation(Class<? extends Annotation> clazz, Element element,
            XMLContext.Default defaults) {
        Element subelement = element == null ? null : element.element(annotationToXml.get(clazz));
        if (subelement != null) {
            return AnnotationFactory.create(new AnnotationDescriptor(clazz));
        } else if (defaults.canUseJavaAnnotations()) {
            //TODO wonder whether it should be excluded so that user can undone it
            return getJavaAnnotation(clazz);
        } else {
            return null;
        }
    }

    private SqlResultSetMappings getSqlResultSetMappings(Element tree, XMLContext.Default defaults) {
        List<SqlResultSetMapping> results = (List<SqlResultSetMapping>) buildSqlResultsetMappings(tree, defaults);
        if (defaults.canUseJavaAnnotations()) {
            SqlResultSetMapping annotation = getJavaAnnotation(SqlResultSetMapping.class);
            addSqlResultsetMappingIfNeeded(annotation, results);
            SqlResultSetMappings annotations = getJavaAnnotation(SqlResultSetMappings.class);
            if (annotations != null) {
                for (SqlResultSetMapping current : annotations.value()) {
                    addSqlResultsetMappingIfNeeded(current, results);
                }
            }
        }
        if (results.size() > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(SqlResultSetMappings.class);
            ad.setValue("value", results.toArray(new SqlResultSetMapping[results.size()]));
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    public static List<SqlResultSetMapping> buildSqlResultsetMappings(Element element,
            XMLContext.Default defaults) {
        if (element == null)
            return new ArrayList<SqlResultSetMapping>();
        List resultsetElementList = element.elements("sql-result-set-mapping");
        List<SqlResultSetMapping> resultsets = new ArrayList<SqlResultSetMapping>();
        Iterator it = resultsetElementList.listIterator();
        while (it.hasNext()) {
            Element subelement = (Element) it.next();
            AnnotationDescriptor ann = new AnnotationDescriptor(SqlResultSetMapping.class);
            copyStringAttribute(ann, subelement, "name", true);
            List<Element> elements = subelement.elements("entity-result");
            List<EntityResult> entityResults = new ArrayList<EntityResult>(elements.size());
            for (Element entityResult : elements) {
                AnnotationDescriptor entityResultDescriptor = new AnnotationDescriptor(EntityResult.class);
                String clazzName = entityResult.attributeValue("entity-class");
                if (clazzName == null) {
                    throw new AnnotationException("<entity-result> without entity-class. " + SCHEMA_VALIDATION);
                }
                Class clazz;
                try {
                    clazz = ReflectHelper.classForName(XMLContext.buildSafeClassName(clazzName, defaults),
                            JPAOverridenAnnotationReader.class);
                } catch (ClassNotFoundException e) {
                    throw new AnnotationException("Unable to find entity-class: " + clazzName, e);
                }
                entityResultDescriptor.setValue("entityClass", clazz);
                copyStringAttribute(entityResultDescriptor, entityResult, "discriminator-column", false);
                List<FieldResult> fieldResults = new ArrayList<FieldResult>();
                for (Element fieldResult : (List<Element>) entityResult.elements("field-result")) {
                    AnnotationDescriptor fieldResultDescriptor = new AnnotationDescriptor(FieldResult.class);
                    copyStringAttribute(fieldResultDescriptor, fieldResult, "name", true);
                    copyStringAttribute(fieldResultDescriptor, fieldResult, "column", true);
                    fieldResults.add((FieldResult) AnnotationFactory.create(fieldResultDescriptor));
                }
                entityResultDescriptor.setValue("fields",
                        fieldResults.toArray(new FieldResult[fieldResults.size()]));
                entityResults.add((EntityResult) AnnotationFactory.create(entityResultDescriptor));
            }
            ann.setValue("entities", entityResults.toArray(new EntityResult[entityResults.size()]));

            elements = subelement.elements("column-result");
            List<ColumnResult> columnResults = new ArrayList<ColumnResult>(elements.size());
            for (Element columnResult : elements) {
                AnnotationDescriptor columnResultDescriptor = new AnnotationDescriptor(ColumnResult.class);
                copyStringAttribute(columnResultDescriptor, columnResult, "name", true);
                columnResults.add((ColumnResult) AnnotationFactory.create(columnResultDescriptor));
            }
            ann.setValue("columns", columnResults.toArray(new ColumnResult[columnResults.size()]));
            //FIXME there is never such a result-class, get rid of it?
            String clazzName = subelement.attributeValue("result-class");
            if (StringHelper.isNotEmpty(clazzName)) {
                Class clazz;
                try {
                    clazz = ReflectHelper.classForName(XMLContext.buildSafeClassName(clazzName, defaults),
                            JPAOverridenAnnotationReader.class);
                } catch (ClassNotFoundException e) {
                    throw new AnnotationException("Unable to find entity-class: " + clazzName, e);
                }
                ann.setValue("resultClass", clazz);
            }
            copyStringAttribute(ann, subelement, "result-set-mapping", false);
            resultsets.add((SqlResultSetMapping) AnnotationFactory.create(ann));
        }
        return resultsets;
    }

    private void addSqlResultsetMappingIfNeeded(SqlResultSetMapping annotation,
            List<SqlResultSetMapping> resultsets) {
        if (annotation != null) {
            String resultsetName = annotation.name();
            boolean present = false;
            for (SqlResultSetMapping current : resultsets) {
                if (current.name().equals(resultsetName)) {
                    present = true;
                    break;
                }
            }
            if (!present)
                resultsets.add(annotation);
        }
    }

    private NamedQueries getNamedQueries(Element tree, XMLContext.Default defaults) {
        //TODO avoid the Proxy Creation (@NamedQueries) when possible
        List<NamedQuery> queries = (List<NamedQuery>) buildNamedQueries(tree, false, defaults);
        if (defaults.canUseJavaAnnotations()) {
            NamedQuery annotation = getJavaAnnotation(NamedQuery.class);
            addNamedQueryIfNeeded(annotation, queries);
            NamedQueries annotations = getJavaAnnotation(NamedQueries.class);
            if (annotations != null) {
                for (NamedQuery current : annotations.value()) {
                    addNamedQueryIfNeeded(current, queries);
                }
            }
        }
        if (queries.size() > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(NamedQueries.class);
            ad.setValue("value", queries.toArray(new NamedQuery[queries.size()]));
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private void addNamedQueryIfNeeded(NamedQuery annotation, List<NamedQuery> queries) {
        if (annotation != null) {
            String queryName = annotation.name();
            boolean present = false;
            for (NamedQuery current : queries) {
                if (current.name().equals(queryName)) {
                    present = true;
                    break;
                }
            }
            if (!present)
                queries.add(annotation);
        }
    }

    private NamedNativeQueries getNamedNativeQueries(Element tree, XMLContext.Default defaults) {
        List<NamedNativeQuery> queries = (List<NamedNativeQuery>) buildNamedQueries(tree, true, defaults);
        if (defaults.canUseJavaAnnotations()) {
            NamedNativeQuery annotation = getJavaAnnotation(NamedNativeQuery.class);
            addNamedNativeQueryIfNeeded(annotation, queries);
            NamedNativeQueries annotations = getJavaAnnotation(NamedNativeQueries.class);
            if (annotations != null) {
                for (NamedNativeQuery current : annotations.value()) {
                    addNamedNativeQueryIfNeeded(current, queries);
                }
            }
        }
        if (queries.size() > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(NamedNativeQueries.class);
            ad.setValue("value", queries.toArray(new NamedNativeQuery[queries.size()]));
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private void addNamedNativeQueryIfNeeded(NamedNativeQuery annotation, List<NamedNativeQuery> queries) {
        if (annotation != null) {
            String queryName = annotation.name();
            boolean present = false;
            for (NamedNativeQuery current : queries) {
                if (current.name().equals(queryName)) {
                    present = true;
                    break;
                }
            }
            if (!present)
                queries.add(annotation);
        }
    }

    public static List buildNamedQueries(Element element, boolean isNative, XMLContext.Default defaults) {
        if (element == null)
            return new ArrayList();
        List namedQueryElementList = isNative ? element.elements("named-native-query")
                : element.elements("named-query");
        List namedQueries = new ArrayList();
        Iterator it = namedQueryElementList.listIterator();
        while (it.hasNext()) {
            Element subelement = (Element) it.next();
            AnnotationDescriptor ann = new AnnotationDescriptor(
                    isNative ? NamedNativeQuery.class : NamedQuery.class);
            copyStringAttribute(ann, subelement, "name", false);
            Element queryElt = subelement.element("query");
            if (queryElt == null)
                throw new AnnotationException("No <query> element found." + SCHEMA_VALIDATION);
            ann.setValue("query", queryElt.getTextTrim());
            List<Element> elements = subelement.elements("hint");
            List<QueryHint> queryHints = new ArrayList<QueryHint>(elements.size());
            for (Element hint : elements) {
                AnnotationDescriptor hintDescriptor = new AnnotationDescriptor(QueryHint.class);
                String value = hint.attributeValue("name");
                if (value == null)
                    throw new AnnotationException("<hint> without name. " + SCHEMA_VALIDATION);
                hintDescriptor.setValue("name", value);
                value = hint.attributeValue("value");
                if (value == null)
                    throw new AnnotationException("<hint> without value. " + SCHEMA_VALIDATION);
                hintDescriptor.setValue("value", value);
                queryHints.add((QueryHint) AnnotationFactory.create(hintDescriptor));
            }
            ann.setValue("hints", queryHints.toArray(new QueryHint[queryHints.size()]));
            String clazzName = subelement.attributeValue("result-class");
            if (StringHelper.isNotEmpty(clazzName)) {
                Class clazz;
                try {
                    clazz = ReflectHelper.classForName(XMLContext.buildSafeClassName(clazzName, defaults),
                            JPAOverridenAnnotationReader.class);
                } catch (ClassNotFoundException e) {
                    throw new AnnotationException("Unable to find entity-class: " + clazzName, e);
                }
                ann.setValue("resultClass", clazz);
            }
            copyStringAttribute(ann, subelement, "result-set-mapping", false);
            namedQueries.add(AnnotationFactory.create(ann));
        }
        return namedQueries;
    }

    private TableGenerator getTableGenerator(Element tree, XMLContext.Default defaults) {
        Element element = tree != null ? tree.element(annotationToXml.get(TableGenerator.class)) : null;
        if (element != null) {
            return buildTableGeneratorAnnotation(element, defaults);
        } else if (defaults.canUseJavaAnnotations() && isJavaAnnotationPresent(TableGenerator.class)) {
            TableGenerator tableAnn = getJavaAnnotation(TableGenerator.class);
            if (StringHelper.isNotEmpty(defaults.getSchema()) || StringHelper.isNotEmpty(defaults.getCatalog())) {
                AnnotationDescriptor annotation = new AnnotationDescriptor(TableGenerator.class);
                annotation.setValue("name", tableAnn.name());
                annotation.setValue("table", tableAnn.table());
                annotation.setValue("catalog", tableAnn.table());
                if (StringHelper.isEmpty((String) annotation.valueOf("catalog"))
                        && StringHelper.isNotEmpty(defaults.getCatalog())) {
                    annotation.setValue("catalog", defaults.getCatalog());
                }
                annotation.setValue("schema", tableAnn.table());
                if (StringHelper.isEmpty((String) annotation.valueOf("schema"))
                        && StringHelper.isNotEmpty(defaults.getSchema())) {
                    annotation.setValue("catalog", defaults.getSchema());
                }
                annotation.setValue("pkColumnName", tableAnn.pkColumnName());
                annotation.setValue("valueColumnName", tableAnn.valueColumnName());
                annotation.setValue("pkColumnValue", tableAnn.pkColumnValue());
                annotation.setValue("initialValue", tableAnn.initialValue());
                annotation.setValue("allocationSize", tableAnn.allocationSize());
                annotation.setValue("uniqueConstraints", tableAnn.uniqueConstraints());
                return AnnotationFactory.create(annotation);
            } else {
                return tableAnn;
            }
        } else {
            return null;
        }
    }

    public static TableGenerator buildTableGeneratorAnnotation(Element element, XMLContext.Default defaults) {
        AnnotationDescriptor ad = new AnnotationDescriptor(TableGenerator.class);
        copyStringAttribute(ad, element, "name", false);
        copyStringAttribute(ad, element, "table", false);
        copyStringAttribute(ad, element, "catalog", false);
        copyStringAttribute(ad, element, "schema", false);
        copyStringAttribute(ad, element, "pk-column-name", false);
        copyStringAttribute(ad, element, "value-column-name", false);
        copyStringAttribute(ad, element, "pk-column-value", false);
        copyIntegerAttribute(ad, element, "initial-value");
        copyIntegerAttribute(ad, element, "allocation-size");
        buildUniqueConstraints(ad, element);
        if (StringHelper.isEmpty((String) ad.valueOf("schema")) && StringHelper.isNotEmpty(defaults.getSchema())) {
            ad.setValue("schema", defaults.getSchema());
        }
        if (StringHelper.isEmpty((String) ad.valueOf("catalog"))
                && StringHelper.isNotEmpty(defaults.getCatalog())) {
            ad.setValue("catalog", defaults.getCatalog());
        }
        return AnnotationFactory.create(ad);
    }

    private SequenceGenerator getSequenceGenerator(Element tree, XMLContext.Default defaults) {
        Element element = tree != null ? tree.element(annotationToXml.get(SequenceGenerator.class)) : null;
        if (element != null) {
            return buildSequenceGeneratorAnnotation(element);
        } else if (defaults.canUseJavaAnnotations()) {
            return getJavaAnnotation(SequenceGenerator.class);
        } else {
            return null;
        }
    }

    public static SequenceGenerator buildSequenceGeneratorAnnotation(Element element) {
        if (element != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(SequenceGenerator.class);
            copyStringAttribute(ad, element, "name", false);
            copyStringAttribute(ad, element, "sequence-name", false);
            copyIntegerAttribute(ad, element, "initial-value");
            copyIntegerAttribute(ad, element, "allocation-size");
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private DiscriminatorColumn getDiscriminatorColumn(Element tree, XMLContext.Default defaults) {
        Element element = tree != null ? tree.element("discriminator-column") : null;
        if (element != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(DiscriminatorColumn.class);
            copyStringAttribute(ad, element, "name", false);
            copyStringAttribute(ad, element, "column-definition", false);
            String value = element.attributeValue("discriminator-type");
            DiscriminatorType type = DiscriminatorType.STRING;
            if (value != null) {
                if ("STRING".equals(value)) {
                    type = DiscriminatorType.STRING;
                } else if ("CHAR".equals(value)) {
                    type = DiscriminatorType.CHAR;
                } else if ("INTEGER".equals(value)) {
                    type = DiscriminatorType.INTEGER;
                } else {
                    throw new AnnotationException(
                            "Unknown DiscrimiatorType in XML: " + value + " (" + SCHEMA_VALIDATION + ")");
                }
            }
            ad.setValue("discriminatorType", type);
            copyIntegerAttribute(ad, element, "length");
            return AnnotationFactory.create(ad);
        } else if (defaults.canUseJavaAnnotations()) {
            return getJavaAnnotation(DiscriminatorColumn.class);
        } else {
            return null;
        }
    }

    private DiscriminatorValue getDiscriminatorValue(Element tree, XMLContext.Default defaults) {
        Element element = tree != null ? tree.element("discriminator-value") : null;
        if (element != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(DiscriminatorValue.class);
            copyStringElement(element, ad, "value");
            return AnnotationFactory.create(ad);
        } else if (defaults.canUseJavaAnnotations()) {
            return getJavaAnnotation(DiscriminatorValue.class);
        } else {
            return null;
        }
    }

    private Inheritance getInheritance(Element tree, XMLContext.Default defaults) {
        Element element = tree != null ? tree.element("inheritance") : null;
        if (element != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(Inheritance.class);
            Attribute attr = element.attribute("strategy");
            InheritanceType strategy = InheritanceType.SINGLE_TABLE;
            if (attr != null) {
                String value = attr.getValue();
                if ("SINGLE_TABLE".equals(value)) {
                    strategy = InheritanceType.SINGLE_TABLE;
                } else if ("JOINED".equals(value)) {
                    strategy = InheritanceType.JOINED;
                } else if ("TABLE_PER_CLASS".equals(value)) {
                    strategy = InheritanceType.TABLE_PER_CLASS;
                } else {
                    throw new AnnotationException(
                            "Unknown InheritanceType in XML: " + value + " (" + SCHEMA_VALIDATION + ")");
                }
            }
            ad.setValue("strategy", strategy);
            return AnnotationFactory.create(ad);
        } else if (defaults.canUseJavaAnnotations()) {
            return getJavaAnnotation(Inheritance.class);
        } else {
            return null;
        }
    }

    private IdClass getIdClass(Element tree, XMLContext.Default defaults) {
        Element element = tree == null ? null : tree.element("id-class");
        if (element != null) {
            Attribute attr = element.attribute("class");
            if (attr != null) {
                AnnotationDescriptor ad = new AnnotationDescriptor(IdClass.class);
                Class clazz;
                try {
                    clazz = ReflectHelper.classForName(XMLContext.buildSafeClassName(attr.getValue(), defaults),
                            this.getClass());
                } catch (ClassNotFoundException e) {
                    throw new AnnotationException("Unable to find id-class: " + attr.getValue(), e);
                }
                ad.setValue("value", clazz);
                return AnnotationFactory.create(ad);
            } else {
                throw new AnnotationException("id-class without class. " + SCHEMA_VALIDATION);
            }
        } else if (defaults.canUseJavaAnnotations()) {
            return getJavaAnnotation(IdClass.class);
        } else {
            return null;
        }
    }

    private PrimaryKeyJoinColumns getPrimaryKeyJoinColumns(Element element, XMLContext.Default defaults) {
        PrimaryKeyJoinColumn[] columns = buildPrimaryKeyJoinColumns(element);
        if (columns.length == 0 && defaults.canUseJavaAnnotations()) {
            PrimaryKeyJoinColumn annotation = getJavaAnnotation(PrimaryKeyJoinColumn.class);
            if (annotation != null) {
                columns = new PrimaryKeyJoinColumn[] { annotation };
            } else {
                PrimaryKeyJoinColumns annotations = getJavaAnnotation(PrimaryKeyJoinColumns.class);
                columns = annotations != null ? annotations.value() : columns;
            }
        }
        if (columns.length > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(PrimaryKeyJoinColumns.class);
            ad.setValue("value", columns);
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private Entity getEntity(Element tree, XMLContext.Default defaults) {
        if (tree == null) {
            return defaults.canUseJavaAnnotations() ? getJavaAnnotation(Entity.class) : null;
        } else {
            if ("entity".equals(tree.getName())) {
                AnnotationDescriptor entity = new AnnotationDescriptor(Entity.class);
                copyStringAttribute(entity, tree, "name", false);
                if (defaults.canUseJavaAnnotations() && StringHelper.isEmpty((String) entity.valueOf("name"))) {
                    Entity javaAnn = getJavaAnnotation(Entity.class);
                    if (javaAnn != null)
                        entity.setValue("name", javaAnn.name());
                }
                return AnnotationFactory.create(entity);
            } else {
                return null; //this is not an entity
            }
        }
    }

    private MappedSuperclass getMappedSuperclass(Element tree, XMLContext.Default defaults) {
        if (tree == null) {
            return defaults.canUseJavaAnnotations() ? getJavaAnnotation(MappedSuperclass.class) : null;
        } else {
            if ("mapped-superclass".equals(tree.getName())) {
                AnnotationDescriptor entity = new AnnotationDescriptor(MappedSuperclass.class);
                return AnnotationFactory.create(entity);
            } else {
                return null; //this is not an entity
            }
        }
    }

    private Embeddable getEmbeddable(Element tree, XMLContext.Default defaults) {
        if (tree == null) {
            return defaults.canUseJavaAnnotations() ? getJavaAnnotation(Embeddable.class) : null;
        } else {
            if ("embeddable".equals(tree.getName())) {
                AnnotationDescriptor entity = new AnnotationDescriptor(Embeddable.class);
                return AnnotationFactory.create(entity);
            } else {
                return null; //this is not an entity
            }
        }
    }

    private Table getTable(Element tree, XMLContext.Default defaults) {
        Element subelement = tree == null ? null : tree.element("table");
        if (subelement == null) {
            //no element but might have some default or some annotation
            if (StringHelper.isNotEmpty(defaults.getCatalog()) || StringHelper.isNotEmpty(defaults.getSchema())) {
                AnnotationDescriptor annotation = new AnnotationDescriptor(Table.class);
                if (defaults.canUseJavaAnnotations()) {
                    Table table = getJavaAnnotation(Table.class);
                    if (table != null) {
                        annotation.setValue("name", table.name());
                        annotation.setValue("schema", table.schema());
                        annotation.setValue("catalog", table.catalog());
                        annotation.setValue("uniqueConstraints", table.uniqueConstraints());
                    }
                }
                if (StringHelper.isEmpty((String) annotation.valueOf("schema"))
                        && StringHelper.isNotEmpty(defaults.getSchema())) {
                    annotation.setValue("schema", defaults.getSchema());
                }
                if (StringHelper.isEmpty((String) annotation.valueOf("catalog"))
                        && StringHelper.isNotEmpty(defaults.getCatalog())) {
                    annotation.setValue("catalog", defaults.getCatalog());
                }
                return AnnotationFactory.create(annotation);
            } else if (defaults.canUseJavaAnnotations()) {
                return getJavaAnnotation(Table.class);
            } else {
                return null;
            }
        } else {
            //ignore java annotation, an element is defined
            AnnotationDescriptor annotation = new AnnotationDescriptor(Table.class);
            copyStringAttribute(annotation, subelement, "name", false);
            copyStringAttribute(annotation, subelement, "catalog", false);
            if (StringHelper.isNotEmpty(defaults.getCatalog())
                    && StringHelper.isEmpty((String) annotation.valueOf("catalog"))) {
                annotation.setValue("catalog", defaults.getCatalog());
            }
            copyStringAttribute(annotation, subelement, "schema", false);
            if (StringHelper.isNotEmpty(defaults.getSchema())
                    && StringHelper.isEmpty((String) annotation.valueOf("schema"))) {
                annotation.setValue("schema", defaults.getSchema());
            }
            buildUniqueConstraints(annotation, subelement);
            return AnnotationFactory.create(annotation);
        }
    }

    private SecondaryTables getSecondaryTables(Element tree, XMLContext.Default defaults) {
        List<Element> elements = tree == null ? new ArrayList<Element>()
                : (List<Element>) tree.elements("secondary-table");
        List<SecondaryTable> secondaryTables = new ArrayList<SecondaryTable>(3);
        for (Element element : elements) {
            AnnotationDescriptor annotation = new AnnotationDescriptor(SecondaryTable.class);
            copyStringAttribute(annotation, element, "name", false);
            copyStringAttribute(annotation, element, "catalog", false);
            if (StringHelper.isNotEmpty(defaults.getCatalog())
                    && StringHelper.isEmpty((String) annotation.valueOf("catalog"))) {
                annotation.setValue("catalog", defaults.getCatalog());
            }
            copyStringAttribute(annotation, element, "schema", false);
            if (StringHelper.isNotEmpty(defaults.getSchema())
                    && StringHelper.isEmpty((String) annotation.valueOf("schema"))) {
                annotation.setValue("schema", defaults.getSchema());
            }
            buildUniqueConstraints(annotation, element);
            annotation.setValue("pkJoinColumns", buildPrimaryKeyJoinColumns(element));
            secondaryTables.add((SecondaryTable) AnnotationFactory.create(annotation));
        }
        /*
         * You can't have both secondary table in XML and Java,
         * since there would be no way to "remove" a secondary table
         */
        if (secondaryTables.size() == 0 && defaults.canUseJavaAnnotations()) {
            SecondaryTable secTableAnn = getJavaAnnotation(SecondaryTable.class);
            overridesDefaultInSecondaryTable(secTableAnn, defaults, secondaryTables);
            SecondaryTables secTablesAnn = getJavaAnnotation(SecondaryTables.class);
            if (secTablesAnn != null) {
                for (SecondaryTable table : secTablesAnn.value()) {
                    overridesDefaultInSecondaryTable(table, defaults, secondaryTables);
                }
            }
        }
        if (secondaryTables.size() > 0) {
            AnnotationDescriptor descriptor = new AnnotationDescriptor(SecondaryTables.class);
            descriptor.setValue("value", secondaryTables.toArray(new SecondaryTable[secondaryTables.size()]));
            return AnnotationFactory.create(descriptor);
        } else {
            return null;
        }
    }

    private void overridesDefaultInSecondaryTable(SecondaryTable secTableAnn, XMLContext.Default defaults,
            List<SecondaryTable> secondaryTables) {
        if (secTableAnn != null) {
            //handle default values
            if (StringHelper.isNotEmpty(defaults.getCatalog()) || StringHelper.isNotEmpty(defaults.getSchema())) {
                AnnotationDescriptor annotation = new AnnotationDescriptor(SecondaryTable.class);
                annotation.setValue("name", secTableAnn.name());
                annotation.setValue("schema", secTableAnn.schema());
                annotation.setValue("catalog", secTableAnn.catalog());
                annotation.setValue("uniqueConstraints", secTableAnn.uniqueConstraints());
                annotation.setValue("pkJoinColumns", secTableAnn.pkJoinColumns());
                if (StringHelper.isEmpty((String) annotation.valueOf("schema"))
                        && StringHelper.isNotEmpty(defaults.getSchema())) {
                    annotation.setValue("schema", defaults.getSchema());
                }
                if (StringHelper.isEmpty((String) annotation.valueOf("catalog"))
                        && StringHelper.isNotEmpty(defaults.getCatalog())) {
                    annotation.setValue("catalog", defaults.getCatalog());
                }
                secondaryTables.add((SecondaryTable) AnnotationFactory.create(annotation));
            } else {
                secondaryTables.add(secTableAnn);
            }
        }
    }

    private static void buildUniqueConstraints(AnnotationDescriptor annotation, Element element) {
        List uniqueConstraintElementList = element.elements("unique-constraint");
        UniqueConstraint[] uniqueConstraints = new UniqueConstraint[uniqueConstraintElementList.size()];
        int ucIndex = 0;
        Iterator ucIt = uniqueConstraintElementList.listIterator();
        while (ucIt.hasNext()) {
            Element subelement = (Element) ucIt.next();
            List<Element> columnNamesElements = subelement.elements("column-name");
            String[] columnNames = new String[columnNamesElements.size()];
            int columnNameIndex = 0;
            Iterator it = columnNamesElements.listIterator();
            while (it.hasNext()) {
                Element columnNameElt = (Element) it.next();
                columnNames[columnNameIndex++] = columnNameElt.getTextTrim();
            }
            AnnotationDescriptor ucAnn = new AnnotationDescriptor(UniqueConstraint.class);
            ucAnn.setValue("columnNames", columnNames);
            uniqueConstraints[ucIndex++] = AnnotationFactory.create(ucAnn);
        }
        annotation.setValue("uniqueConstraints", uniqueConstraints);
    }

    private PrimaryKeyJoinColumn[] buildPrimaryKeyJoinColumns(Element element) {
        if (element == null)
            return new PrimaryKeyJoinColumn[] {};
        List pkJoinColumnElementList = element.elements("primary-key-join-column");
        PrimaryKeyJoinColumn[] pkJoinColumns = new PrimaryKeyJoinColumn[pkJoinColumnElementList.size()];
        int index = 0;
        Iterator pkIt = pkJoinColumnElementList.listIterator();
        while (pkIt.hasNext()) {
            Element subelement = (Element) pkIt.next();
            AnnotationDescriptor pkAnn = new AnnotationDescriptor(PrimaryKeyJoinColumn.class);
            copyStringAttribute(pkAnn, subelement, "name", false);
            copyStringAttribute(pkAnn, subelement, "referenced-column-name", false);
            copyStringAttribute(pkAnn, subelement, "column-definition", false);
            pkJoinColumns[index++] = AnnotationFactory.create(pkAnn);
        }
        return pkJoinColumns;
    }

    private static void copyStringAttribute(AnnotationDescriptor annotation, Element element, String attributeName,
            boolean mandatory) {
        String attribute = element.attributeValue(attributeName);
        if (attribute != null) {
            String annotationAttributeName = getJavaAttributeNameFromXMLOne(attributeName);
            annotation.setValue(annotationAttributeName, attribute);
        } else {
            if (mandatory) {
                throw new AnnotationException(element.getName() + "." + attributeName
                        + " is mandatory in XML overring. " + SCHEMA_VALIDATION);
            }
        }
    }

    private static void copyIntegerAttribute(AnnotationDescriptor annotation, Element element,
            String attributeName) {
        String attribute = element.attributeValue(attributeName);
        if (attribute != null) {
            String annotationAttributeName = getJavaAttributeNameFromXMLOne(attributeName);
            annotation.setValue(annotationAttributeName, attribute);
            try {
                int length = Integer.parseInt(attribute);
                annotation.setValue(annotationAttributeName, length);
            } catch (NumberFormatException e) {
                throw new AnnotationException(element.getPath() + attributeName + " not parseable: " + attribute
                        + " (" + SCHEMA_VALIDATION + ")");
            }
        }
    }

    private static String getJavaAttributeNameFromXMLOne(String attributeName) {
        StringBuilder annotationAttributeName = new StringBuilder(attributeName);
        int index = annotationAttributeName.indexOf(WORD_SEPARATOR);
        while (index != -1) {
            annotationAttributeName.deleteCharAt(index);
            annotationAttributeName.setCharAt(index, Character.toUpperCase(annotationAttributeName.charAt(index)));
            index = annotationAttributeName.indexOf(WORD_SEPARATOR);
        }
        return annotationAttributeName.toString();
    }

    private static void copyStringElement(Element element, AnnotationDescriptor ad, String annotationAttribute) {
        String discr = element.getTextTrim();
        ad.setValue(annotationAttribute, discr);
    }

    private static void copyBooleanAttribute(AnnotationDescriptor descriptor, Element element, String attribute) {
        String attributeValue = element.attributeValue(attribute);
        if (StringHelper.isNotEmpty(attributeValue)) {
            String javaAttribute = getJavaAttributeNameFromXMLOne(attribute);
            descriptor.setValue(javaAttribute, Boolean.parseBoolean(attributeValue));
        }
    }

    private <T extends Annotation> T getJavaAnnotation(Class<T> annotationType) {
        return element.getAnnotation(annotationType);
    }

    private <T extends Annotation> boolean isJavaAnnotationPresent(Class<T> annotationType) {
        return element.isAnnotationPresent(annotationType);
    }

    private Annotation[] getJavaAnnotations() {
        return element.getAnnotations();
    }
}