org.batoo.jpa.core.impl.model.EntityTypeImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.batoo.jpa.core.impl.model.EntityTypeImpl.java

Source

/*
 * Copyright (c) 2012-2013, Batu Alp Ceylan
 *
 * 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.batoo.jpa.core.impl.model;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.naming.directory.BasicAttribute;
import javax.persistence.InheritanceType;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceException;
import javax.persistence.criteria.Fetch;
import javax.persistence.criteria.FetchParent;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Path;
import javax.persistence.metamodel.Attribute.PersistentAttributeType;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.SingularAttribute;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.mutable.MutableBoolean;
import org.batoo.common.reflect.AbstractAccessor;
import org.batoo.common.reflect.ConstructorAccessor;
import org.batoo.common.reflect.ReflectHelper;
import org.batoo.common.util.BatooUtils;
import org.batoo.common.util.FinalWrapper;
import org.batoo.jpa.annotations.FetchStrategyType;
import org.batoo.jpa.core.impl.criteria.CriteriaBuilderImpl;
import org.batoo.jpa.core.impl.criteria.CriteriaQueryImpl;
import org.batoo.jpa.core.impl.criteria.QueryImpl;
import org.batoo.jpa.core.impl.criteria.RootImpl;
import org.batoo.jpa.core.impl.criteria.expression.ParameterExpressionImpl;
import org.batoo.jpa.core.impl.criteria.expression.PredicateImpl;
import org.batoo.jpa.core.impl.instance.EnhancedInstance;
import org.batoo.jpa.core.impl.instance.Enhancer;
import org.batoo.jpa.core.impl.instance.ManagedId;
import org.batoo.jpa.core.impl.instance.ManagedInstance;
import org.batoo.jpa.core.impl.instance.Status;
import org.batoo.jpa.core.impl.manager.EntityManagerFactoryImpl;
import org.batoo.jpa.core.impl.manager.EntityManagerImpl;
import org.batoo.jpa.core.impl.manager.SessionImpl;
import org.batoo.jpa.core.impl.model.attribute.AssociatedSingularAttribute;
import org.batoo.jpa.core.impl.model.attribute.AttributeImpl;
import org.batoo.jpa.core.impl.model.mapping.AbstractMapping;
import org.batoo.jpa.core.impl.model.mapping.AssociationMappingImpl;
import org.batoo.jpa.core.impl.model.mapping.BasicMappingImpl;
import org.batoo.jpa.core.impl.model.mapping.EmbeddedMappingImpl;
import org.batoo.jpa.core.impl.model.mapping.EntityMapping;
import org.batoo.jpa.core.impl.model.mapping.JoinedMapping;
import org.batoo.jpa.core.impl.model.mapping.PluralAssociationMappingImpl;
import org.batoo.jpa.core.impl.model.mapping.PluralMappingEx;
import org.batoo.jpa.core.impl.model.mapping.SingularAssociationMappingImpl;
import org.batoo.jpa.core.impl.model.mapping.SingularMappingEx;
import org.batoo.common.util.Pair;
import org.batoo.jpa.jdbc.AbstractColumn;
import org.batoo.jpa.jdbc.AbstractTable;
import org.batoo.jpa.jdbc.BasicColumn;
import org.batoo.jpa.jdbc.DiscriminatorColumn;
import org.batoo.jpa.jdbc.EntityTable;
import org.batoo.jpa.jdbc.IdType;
import org.batoo.jpa.jdbc.JoinColumn;
import org.batoo.jpa.jdbc.SecondaryTable;
import org.batoo.jpa.jdbc.mapping.Mapping;
import org.batoo.jpa.jdbc.mapping.MappingType;
import org.batoo.jpa.jdbc.mapping.SingularMapping;
import org.batoo.jpa.jdbc.model.EntityTypeDescriptor;
import org.batoo.jpa.parser.MappingException;
import org.batoo.jpa.parser.metadata.AssociationMetadata;
import org.batoo.jpa.parser.metadata.AttributeOverrideMetadata;
import org.batoo.jpa.parser.metadata.ColumnMetadata;
import org.batoo.jpa.parser.metadata.EntityListenerMetadata.EntityListenerType;
import org.batoo.jpa.parser.metadata.IndexMetadata;
import org.batoo.jpa.parser.metadata.SecondaryTableMetadata;
import org.batoo.jpa.parser.metadata.type.EntityMetadata;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * Implementation of {@link EntityType}.
 * 
 * @param <X>
 *            The represented entity type
 * 
 * @author hceylan
 * @since 2.0.0
 */
public class EntityTypeImpl<X> extends IdentifiableTypeImpl<X> implements EntityType<X>, EntityTypeDescriptor {

    private final EntityMetadata metadata;
    private final String name;
    private EntityTable primaryTable;
    private final Map<String, EntityTable> tableMap = Maps.newHashMap();
    private FinalWrapper<EntityTable[]> tables;
    private FinalWrapper<EntityTable[]> updateTables;
    private FinalWrapper<EntityTable[]> allTables;
    private final HashMap<String, AssociatedSingularAttribute<? super X, ?>> idMap = Maps.newHashMap();

    private final ConstructorAccessor constructor;

    private CriteriaQueryImpl<X> selectCriteria;
    private CriteriaQueryImpl<X> refreshCriteria;
    private int dependencyCount;
    private boolean canBatchRemoves;

    private final HashMap<EntityTypeImpl<?>, AssociationMappingImpl<?, ?, ?>[]> dependencyMap = Maps.newHashMap();
    private FinalWrapper<BasicMappingImpl<?, ?>[]> basicMappingImpls;

    private FinalWrapper<AbstractMapping<?, ?, ?>[]> singularMappings;
    private FinalWrapper<PluralMappingEx<?, ?, ?>[]> mappingsPluralSorted;
    private FinalWrapper<PluralMappingEx<?, ?, ?>[]> mappingsPlural;
    private FinalWrapper<JoinedMapping<?, ?, ?>[]> mappingsJoined;
    private FinalWrapper<AssociationMappingImpl<?, ?, ?>[]> associations;
    private FinalWrapper<AssociationMappingImpl<?, ?, ?>[]> associationsDetachable;
    private FinalWrapper<AssociationMappingImpl<?, ?, ?>[]> associationsJoined;
    private FinalWrapper<AssociationMappingImpl<?, ?, ?>[]> associationsNotPersistable;
    private FinalWrapper<AssociationMappingImpl<?, ?, ?>[]> associationsPersistable;
    private FinalWrapper<AssociationMappingImpl<?, ?, ?>[]> associationsRemovable;
    private FinalWrapper<PluralAssociationMappingImpl<?, ?, ?>[]> associationsPlural;
    private FinalWrapper<SingularAssociationMappingImpl<?, ?>[]> associationsSingular;
    private FinalWrapper<SingularAssociationMappingImpl<?, ?>[]> associationsSingularLazy;
    private final Map<Method, Method> idMethods = Maps.newHashMap();

    private SingularMappingEx<? super X, ?> idMapping;
    private Boolean suitableForBatchInsert;

    private Pair<SingularMapping<?, ?>, AbstractAccessor>[] idMappings;
    private InheritanceType inheritanceType;

    private final Map<String, EntityTypeImpl<? extends X>> children = Maps.newHashMap();
    private final String discriminatorValue;
    private DiscriminatorColumn discriminatorColumn;
    private EntityTypeImpl<? super X> rootType;
    private final EntityMapping<X> entityMapping;

    private final List<IndexMetadata> indexes;
    private final int maxFetchJoinDepth;

    /**
     * @param metamodel
     *            the metamodel
     * @param parent
     *            the parent type
     * @param javaType
     *            the java type of the managed type
     * @param metadata
     *            the metadata
     * 
     * @since 2.0.0
     */
    public EntityTypeImpl(MetamodelImpl metamodel, IdentifiableTypeImpl<? super X> parent, Class<X> javaType,
            EntityMetadata metadata) {
        super(metamodel, parent, javaType, metadata);

        this.name = metadata.getName();
        this.metadata = metadata;
        this.indexes = metadata.getIndexes();
        this.inheritanceType = metadata.getInheritanceType();
        this.discriminatorValue = StringUtils.isNotBlank(metadata.getDiscriminatorValue())
                ? metadata.getDiscriminatorValue()
                : this.name;
        this.maxFetchJoinDepth = metamodel.getEntityManagerFactory().getMaxFetchJoinDepth();

        this.addAttributes(metadata);
        this.initTables(metadata);
        this.entityMapping = new EntityMapping<X>(this);
        this.linkMappings();
        this.initIndexes();

        if (metadata.getTableGenerator() != null) {
            metamodel.addTableGenerator(metadata.getTableGenerator());
        }

        if (metadata.getSequenceGenerator() != null) {
            metamodel.addSequenceGenerator(metadata.getSequenceGenerator());
        }

        this.constructor = this.enhance();
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    protected void addAttribute(AttributeImpl<? super X, ?> attribute) {
        super.addAttribute(attribute);

        if ((attribute.getPersistentAttributeType() == PersistentAttributeType.MANY_TO_ONE)
                || (attribute.getPersistentAttributeType() == PersistentAttributeType.ONE_TO_ONE)) {
            final AssociatedSingularAttribute<? super X, ?> singularAttribute = (AssociatedSingularAttribute<? super X, ?>) attribute;
            if (StringUtils.isNotBlank(singularAttribute.getMapsId())) {
                this.idMap.put(singularAttribute.getMapsId(), singularAttribute);
            }
        }
    }

    /**
     * Returns if remove operation can be combined into a batch.
     * <p>
     * The remove operation can be combined into a batch provided:
     * <ul>
     * <li>the entity has no version attribute
     * <li>the entity has single basic id type.
     * 
     * @return <code>true</code> if remove operation can be combined into a batch, <code>false</code> otherwise
     * 
     * @since 2.0.1
     */
    public boolean canBatchRemoves() {
        return this.canBatchRemoves;
    }

    private ConstructorAccessor enhance() {
        try {
            final Class<X> enhancedClass = Enhancer.enhance(this);
            final Constructor<X> constructor = enhancedClass.getConstructor(Class.class, // type
                    SessionImpl.class, // session
                    Object.class, // id
                    Boolean.TYPE); // initialized

            return ReflectHelper.createConstructor(constructor);
        } catch (final Exception e) {
            throw new RuntimeException("Cannot enhance class: " + this.getJavaType(), e);
        }
    }

    /**
     * Returns if this entity extends the parent entity.
     * 
     * @param parent
     *            the parent to test
     * @return true if this entity extends the parent entity, false otherwise
     * 
     * @since 2.0.0
     */
    public boolean extendz(EntityTypeImpl<?> parent) {
        IdentifiableTypeImpl<? super X> supertype = this;

        do {
            if (supertype == parent) {
                return true;
            }
            supertype = supertype.getSupertype();
        } while (supertype != null);

        return false;
    }

    /**
     * Fires the callbacks.
     * 
     * @param instance
     *            the instance
     * @param type
     *            the type
     * 
     * @since 2.0.0
     */
    public void fireCallbacks(Object instance, EntityListenerType type) {
        this.fireCallbacks(true, instance, type);
    }

    /**
     * Returns all the tables in the inheritance chain.
     * 
     * @return the array of tables
     * 
     * @since 2.0.0
     */
    public EntityTable[] getAllTables() {
        FinalWrapper<EntityTable[]> wrapper = this.allTables;

        if (wrapper == null) {
            synchronized (this) {
                if (this.allTables == null) {

                    final Map<String, EntityTable> _tableMap = Maps.newHashMap();
                    this.getAllTables(_tableMap);

                    final EntityTable[] _tables = new EntityTable[_tableMap.size()];
                    _tableMap.values().toArray(_tables);

                    Arrays.sort(_tables, new Comparator<EntityTable>() {

                        @Override
                        public int compare(EntityTable o1, EntityTable o2) {
                            if ((o1 instanceof SecondaryTable) && !(o2 instanceof SecondaryTable)) {
                                return 1;
                            }

                            if ((o2 instanceof SecondaryTable) && !(o1 instanceof SecondaryTable)) {
                                return -1;
                            }

                            return o1.getName().compareTo(o2.getName());
                        }
                    });

                    this.allTables = new FinalWrapper<EntityTable[]>(_tables);
                }

                wrapper = this.allTables;
            }
        }

        return wrapper.value;
    }

    private void getAllTables(final Map<String, EntityTable> tableMap) {
        tableMap.putAll(this.tableMap);

        for (final EntityTypeImpl<? extends X> child : this.children.values()) {
            if (child != this) {
                child.getAllTables(tableMap);
                tableMap.putAll(child.tableMap);
            }
        }
    }

    /**
     * Returns if attribute with the <code>path</code> is overridden by the entity.
     * 
     * @param path
     *            the path of the attribute
     * @return the association metadata or <code>null</code>
     * 
     * @since 2.0.0
     */
    public AssociationMetadata getAssociationOverride(String path) {
        for (final AssociationMetadata override : this.metadata.getAssociationOverrides()) {
            if (override.getName().equals(path)) {
                return override;
            }
        }

        return null;
    }

    /**
     * Returns the associations of the type.
     * 
     * @return the associations of the type
     * 
     * @since 2.0.0
     */
    public AssociationMappingImpl<?, ?, ?>[] getAssociations() {
        FinalWrapper<AssociationMappingImpl<?, ?, ?>[]> wrapper = this.associations;

        if (wrapper == null) {
            synchronized (this) {
                if (this.associations == null) {

                    final List<AssociationMappingImpl<?, ?, ?>> _associations = Lists.newArrayList();

                    this.entityMapping.addAssociations(_associations);

                    final AssociationMappingImpl<?, ?, ?>[] __associatedAttributes = new AssociationMappingImpl[_associations
                            .size()];
                    _associations.toArray(__associatedAttributes);

                    this.associations = new FinalWrapper<AssociationMappingImpl<?, ?, ?>[]>(__associatedAttributes);
                }

                wrapper = this.associations;
            }
        }

        return wrapper.value;
    }

    /**
     * Returns the associated attributes that are detachable by the type.
     * 
     * @return the associated attributes that are detachable by the type
     * 
     * @since 2.0.0
     */
    public AssociationMappingImpl<?, ?, ?>[] getAssociationsDetachable() {
        FinalWrapper<AssociationMappingImpl<?, ?, ?>[]> wrapper = this.associationsDetachable;

        if (wrapper == null) {
            synchronized (this) {
                if (this.associationsDetachable == null) {

                    final List<AssociationMappingImpl<?, ?, ?>> _associationsDetachable = Lists.newArrayList();

                    for (final AssociationMappingImpl<?, ?, ?> association : this.getAssociations()) {
                        if (association.cascadesDetach()) {
                            _associationsDetachable.add(association);
                        }
                    }

                    final AssociationMappingImpl<?, ?, ?>[] __associationsDetachable = new AssociationMappingImpl[_associationsDetachable
                            .size()];
                    _associationsDetachable.toArray(__associationsDetachable);

                    this.associationsDetachable = new FinalWrapper<AssociationMappingImpl<?, ?, ?>[]>(
                            __associationsDetachable);
                }

                wrapper = this.associationsDetachable;
            }
        }

        return wrapper.value;
    }

    /**
     * Returns the associated attributes that are joined.
     * 
     * @return the associated attributes that are joined
     * 
     * @since 2.0.0
     */
    public AssociationMappingImpl<?, ?, ?>[] getAssociationsJoined() {
        final FinalWrapper<AssociationMappingImpl<?, ?, ?>[]> wrapper = this.associationsJoined;

        if (wrapper == null) {
            synchronized (this) {
                if (this.associationsJoined == null) {

                    final List<AssociationMappingImpl<?, ?, ?>> joinedAssociations = Lists.newArrayList();

                    for (final AssociationMappingImpl<?, ?, ?> association : this.getAssociations()) {
                        if (association.getJoinTable() != null) {
                            joinedAssociations.add(association);
                        }
                    }

                    final AssociationMappingImpl<?, ?, ?>[] __joinedAssociations = new AssociationMappingImpl[joinedAssociations
                            .size()];
                    joinedAssociations.toArray(__joinedAssociations);

                    this.associationsJoined = new FinalWrapper<AssociationMappingImpl<?, ?, ?>[]>(
                            __joinedAssociations);
                }
            }
        }

        return wrapper.value;
    }

    /**
     * Returns the plural associations that are not persistable.
     * 
     * @return the plural associations that are not persistable
     * 
     * @since 2.0.0
     */
    public AssociationMappingImpl<?, ?, ?>[] getAssociationsNotPersistable() {
        FinalWrapper<AssociationMappingImpl<?, ?, ?>[]> wrapper = this.associationsNotPersistable;

        if (wrapper == null) {
            synchronized (this) {
                if (this.associationsNotPersistable == null) {

                    final List<AssociationMappingImpl<?, ?, ?>> _associationsNotPersistable = Lists.newArrayList();
                    for (final AssociationMappingImpl<?, ?, ?> mapping : this.getAssociations()) {
                        // skip persistable associations
                        if (mapping.cascadesPersist()) {
                            continue;
                        }

                        _associationsNotPersistable.add(mapping);
                    }

                    final AssociationMappingImpl<?, ?, ?>[] __associationsNotPersistable = new AssociationMappingImpl[_associationsNotPersistable
                            .size()];
                    _associationsNotPersistable.toArray(__associationsNotPersistable);

                    this.associationsNotPersistable = new FinalWrapper<AssociationMappingImpl<?, ?, ?>[]>(
                            __associationsNotPersistable);
                }

                wrapper = this.associationsNotPersistable;
            }
        }

        return wrapper.value;
    }

    /**
     * Returns the associated attributes that are persistable by the type.
     * 
     * @return the associated attributes that are persistable by the type
     * 
     * @since 2.0.0
     */
    public AssociationMappingImpl<?, ?, ?>[] getAssociationsPersistable() {
        FinalWrapper<AssociationMappingImpl<?, ?, ?>[]> wrapper = this.associationsPersistable;

        if (wrapper == null) {
            synchronized (this) {
                if (this.associationsPersistable == null) {

                    final List<AssociationMappingImpl<?, ?, ?>> _associationsPersistable = Lists.newArrayList();

                    for (final AssociationMappingImpl<?, ?, ?> association : this.getAssociations()) {
                        if (association.cascadesPersist()) {
                            _associationsPersistable.add(association);
                        }
                    }

                    final AssociationMappingImpl<?, ?, ?>[] __associationsPersistable = new AssociationMappingImpl[_associationsPersistable
                            .size()];
                    _associationsPersistable.toArray(__associationsPersistable);

                    this.associationsPersistable = new FinalWrapper<AssociationMappingImpl<?, ?, ?>[]>(
                            __associationsPersistable);
                }

                wrapper = this.associationsPersistable;
            }
        }

        return wrapper.value;
    }

    /**
     * Returns the plural associations.
     * 
     * @return the plural associations
     * 
     * @since 2.0.0
     */
    public PluralAssociationMappingImpl<?, ?, ?>[] getAssociationsPlural() {
        FinalWrapper<PluralAssociationMappingImpl<?, ?, ?>[]> wrapper = this.associationsPlural;

        if (wrapper == null) {
            synchronized (this) {
                if (this.associationsPlural == null) {

                    final List<PluralAssociationMappingImpl<?, ?, ?>> _associationsPlural = Lists.newArrayList();
                    for (final AssociationMappingImpl<?, ?, ?> mapping : this.getAssociations()) {
                        if (mapping instanceof PluralAssociationMappingImpl) {
                            _associationsPlural.add((PluralAssociationMappingImpl<?, ?, ?>) mapping);
                        }
                    }

                    final PluralAssociationMappingImpl<?, ?, ?>[] __associationsPlural = new PluralAssociationMappingImpl[_associationsPlural
                            .size()];
                    _associationsPlural.toArray(__associationsPlural);

                    this.associationsPlural = new FinalWrapper<PluralAssociationMappingImpl<?, ?, ?>[]>(
                            __associationsPlural);
                }

                wrapper = this.associationsPlural;
            }
        }

        return wrapper.value;
    }

    /**
     * Returns the associated mappings that are removable by the type.
     * 
     * @return the associated mappings that are removable by the type
     * 
     * @since 2.0.0
     */
    public AssociationMappingImpl<?, ?, ?>[] getAssociationsRemovable() {
        FinalWrapper<AssociationMappingImpl<?, ?, ?>[]> wrapper = this.associationsRemovable;

        if (wrapper == null) {
            synchronized (this) {
                if (this.associationsRemovable == null) {

                    final List<AssociationMappingImpl<?, ?, ?>> _associationsRemovable = Lists.newArrayList();

                    for (final AssociationMappingImpl<?, ?, ?> association : this.getAssociations()) {
                        if (association.cascadesRemove() || association.removesOrphans()) {
                            _associationsRemovable.add(association);
                        }
                    }

                    final AssociationMappingImpl<?, ?, ?>[] __associationsRemovable = new AssociationMappingImpl[_associationsRemovable
                            .size()];
                    _associationsRemovable.toArray(__associationsRemovable);

                    this.associationsRemovable = new FinalWrapper<AssociationMappingImpl<?, ?, ?>[]>(
                            __associationsRemovable);
                }

                wrapper = this.associationsRemovable;
            }
        }

        return wrapper.value;
    }

    /**
     * Returns the singular associated mappings.
     * 
     * @return the singular associated mappings
     * 
     * @since 2.0.0
     */
    public SingularAssociationMappingImpl<?, ?>[] getAssociationsSingular() {
        FinalWrapper<SingularAssociationMappingImpl<?, ?>[]> wrapper = this.associationsSingular;

        if (wrapper == null) {
            synchronized (this) {
                if (this.associationsSingular == null) {

                    final List<SingularAssociationMappingImpl<?, ?>> _associationsSingular = Lists.newArrayList();

                    for (final AssociationMappingImpl<?, ?, ?> association : this.getAssociations()) {
                        if (association instanceof SingularAssociationMappingImpl) {
                            _associationsSingular.add((SingularAssociationMappingImpl<?, ?>) association);
                        }
                    }

                    final SingularAssociationMappingImpl<?, ?>[] __associationsSingular = new SingularAssociationMappingImpl[_associationsSingular
                            .size()];
                    _associationsSingular.toArray(__associationsSingular);

                    this.associationsSingular = new FinalWrapper<SingularAssociationMappingImpl<?, ?>[]>(
                            __associationsSingular);
                }

                wrapper = this.associationsSingular;
            }
            ;
        }

        return wrapper.value;
    }

    /**
     * Returns the array of singular owner lazy association of the type.
     * 
     * @return the array of singular owner lazy associations of the type
     * 
     * @since 2.0.0
     */
    public SingularAssociationMappingImpl<?, ?>[] getAssociationsSingularOwnerLazy() {
        FinalWrapper<SingularAssociationMappingImpl<?, ?>[]> wrapper = this.associationsSingularLazy;

        if (wrapper == null) {
            synchronized (this) {
                if (this.associationsSingularLazy == null) {

                    final List<SingularAssociationMappingImpl<?, ?>> _associationsSingularLazy = Lists
                            .newArrayList();
                    for (final AssociationMappingImpl<?, ?, ?> mapping : this.getAssociations()) {
                        if (mapping instanceof SingularAssociationMappingImpl) {
                            final SingularAssociationMappingImpl<?, ?> singularMapping = (SingularAssociationMappingImpl<?, ?>) mapping;
                            if (singularMapping.isOwner() && !singularMapping.isEager()) {
                                _associationsSingularLazy.add(singularMapping);
                            }
                        }
                    }

                    final SingularAssociationMappingImpl<?, ?>[] __associationsSingularLazy = new SingularAssociationMappingImpl[_associationsSingularLazy
                            .size()];
                    _associationsSingularLazy.toArray(__associationsSingularLazy);

                    this.associationsSingularLazy = new FinalWrapper<SingularAssociationMappingImpl<?, ?>[]>(
                            __associationsSingularLazy);
                }

                wrapper = this.associationsSingularLazy;
            }
        }

        return wrapper.value;
    }

    /**
     * Returns if attribute with the <code>path</code> is overridden by the entity.
     * 
     * @param path
     *            the path of the attribute
     * @return the column metadata or <code>null</code>
     * 
     * @since 2.0.0
     */
    public ColumnMetadata getAttributeOverride(String path) {
        for (final AttributeOverrideMetadata override : this.metadata.getAttributeOverrides()) {
            if (override.getName().equals(path)) {
                return override.getColumn();
            }
        }

        return null;
    }

    /**
     * Returns the basic mappings of the type.
     * 
     * @return the basic mappings of the type
     * 
     * @since 2.0.0
     */
    public BasicMappingImpl<?, ?>[] getBasicMappings() {
        FinalWrapper<BasicMappingImpl<?, ?>[]> wrapper = this.basicMappingImpls;

        if (wrapper == null) {
            synchronized (this) {
                if (this.basicMappingImpls == null) {

                    final List<BasicMappingImpl<?, ?>> _basicMappings = Lists.newArrayList();

                    this.entityMapping.addBasicMappings(_basicMappings);

                    final BasicMappingImpl<?, ?>[] __basicMappings = new BasicMappingImpl[_basicMappings.size()];
                    _basicMappings.toArray(__basicMappings);

                    this.basicMappingImpls = new FinalWrapper<BasicMappingImpl<?, ?>[]>(__basicMappings);
                }

                wrapper = this.basicMappingImpls;
            }
        }

        return wrapper.value;
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    public Class<X> getBindableJavaType() {
        return this.getJavaType();
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    public BindableType getBindableType() {
        return BindableType.ENTITY_TYPE;
    }

    /**
     * Returns the child based on the <code>discriminatorValue</code> value.
     * 
     * @param discriminatorValue
     *            the discriminator value of the child
     * @return the child type
     * 
     * @since 2.0.0
     */
    public EntityTypeImpl<? extends X> getChildType(String discriminatorValue) {
        if (discriminatorValue.equals(this.discriminatorValue)) {
            return this;
        }

        return this.children.get(discriminatorValue);
    }

    private CriteriaQueryImpl<X> getCriteriaRefresh() {
        if (this.refreshCriteria != null) {
            return this.refreshCriteria;
        }

        synchronized (this) {
            // other thread prepared before this one
            if (this.refreshCriteria != null) {
                return this.refreshCriteria;
            }

            final CriteriaBuilderImpl cb = this.getMetamodel().getEntityManagerFactory().getCriteriaBuilder();
            CriteriaQueryImpl<X> q = cb.createQuery(this.getJavaType());
            q.internal();
            final RootImpl<X> r = q.from(this);
            q = q.select(r);
            r.alias(BatooUtils.acronym(this.name).toLowerCase());

            // has single id mapping
            if (this.getRootType().hasSingleIdAttribute()) {
                final SingularMappingEx<? super X, ?> idMapping = this.getRootType().getIdMapping();
                final ParameterExpressionImpl<?> pe = cb.parameter(idMapping.getAttribute().getJavaType());
                final Path<?> path = r.get(idMapping.getAttribute().getName());
                final PredicateImpl predicate = cb.equal(path, pe);

                return this.refreshCriteria = q.where(predicate);
            }

            // has multiple id mappings
            final List<PredicateImpl> predicates = Lists.newArrayList();
            for (final Pair<SingularMapping<?, ?>, AbstractAccessor> pair : this.getIdMappings()) {
                final SingularMapping<?, ?> _idMapping = pair.getFirst();
                final ParameterExpressionImpl<?> pe = cb.parameter(_idMapping.getJavaType());

                final Path<?> path = r.get(_idMapping.getName());
                final PredicateImpl predicate = cb.equal(path, pe);

                predicates.add(predicate);
            }

            return this.refreshCriteria = q.where(predicates.toArray(new PredicateImpl[predicates.size()]));
        }
    }

    private CriteriaQueryImpl<X> getCriteriaSelect() {
        if (this.selectCriteria != null) {
            return this.selectCriteria;
        }

        synchronized (this) {
            // other thread prepared before this one
            if (this.selectCriteria != null) {
                return this.selectCriteria;
            }

            final CriteriaBuilderImpl cb = this.getMetamodel().getEntityManagerFactory().getCriteriaBuilder();
            CriteriaQueryImpl<X> q = cb.createQuery(this.getJavaType());
            q.internal();
            final RootImpl<X> r = q.from(this);
            q = q.select(r);
            r.alias(BatooUtils.acronym(this.name).toLowerCase());

            this.prepareEagerJoins(r, 0, null);

            // has single id mapping
            if (this.getRootType().hasSingleIdAttribute()) {
                final SingularMappingEx<? super X, ?> _idMapping = this.getRootType().getIdMapping();
                final ParameterExpressionImpl<?> pe = cb.parameter(_idMapping.getAttribute().getJavaType());
                final Path<?> path = r.get(_idMapping.getAttribute().getName());
                final PredicateImpl predicate = cb.equal(path, pe);

                this.selectCriteria = q.where(predicate);

                return this.selectCriteria;
            }

            // has multiple id mappings
            final List<PredicateImpl> predicates = Lists.newArrayList();
            for (final Pair<SingularMapping<?, ?>, AbstractAccessor> pair : this.getIdMappings()) {
                final SingularMapping<?, ?> _idMapping = pair.getFirst();
                final ParameterExpressionImpl<?> pe = cb.parameter(_idMapping.getJavaType());

                final Path<?> path = r.get(_idMapping.getName());
                final PredicateImpl predicate = cb.equal(path, pe);

                predicates.add(predicate);
            }

            this.selectCriteria = q.where(predicates.toArray(new PredicateImpl[predicates.size()]));

            return this.selectCriteria;
        }
    }

    /**
     * Returns the dependencies for the associate type
     * 
     * @param associate
     *            the associate type
     * @return the array of associations for the associate
     * 
     * @since 2.0.0
     */
    public AssociationMappingImpl<?, ?, ?>[] getDependenciesFor(EntityTypeImpl<?> associate) {
        return this.dependencyMap.get(associate);
    }

    /**
     * Returns the dependencyCount.
     * 
     * @return the dependencyCount
     * @since 2.0.0
     */
    public int getDependencyCount() {
        return this.dependencyCount;
    }

    /**
     * Returns the discriminator column of the entity.
     * 
     * @return the discriminator column of the entity
     * 
     * @since 2.0.0
     */
    public DiscriminatorColumn getDiscriminatorColumn() {
        return this.discriminatorColumn;
    }

    /**
     * Returns the set of discriminator values in the range of this entity's hierarchy.
     * 
     * @return the set of discriminator values
     * 
     * @since 2.0.0
     */
    public Set<String> getDiscriminators() {
        return this.children.keySet();
    }

    /**
     * Returns the discriminatorValue of the EntityTypeImpl.
     * 
     * @return the discriminatorValue of the EntityTypeImpl
     * 
     * @since 2.0.0
     */
    @Override
    public String getDiscriminatorValue() {
        return this.discriminatorValue;
    }

    /**
     * Returns the id of the entity from the instance.
     * 
     * @param instance
     *            the instance
     * @return the managedId or null
     * 
     * @since 2.0.0
     */
    public ManagedId<X> getId(Object instance) {
        Object id;
        final MutableBoolean allNull = new MutableBoolean(true);

        if (this.hasSingleIdAttribute()) {
            id = this.getIdImpl(instance, this.getIdMapping(), allNull);
        } else {
            // create the id class
            id = this.newCompositeId();

            for (final Pair<SingularMapping<?, ?>, AbstractAccessor> pair : this.getIdMappings()) {
                final SingularMapping<?, ?> child = pair.getFirst();

                final Object childId = this.getIdImpl(instance, child, allNull);
                if (childId != null) {
                    allNull.setValue(false);
                }

                pair.getSecond().set(id, childId);
            }
        }

        if (allNull.booleanValue()) {
            return null;
        }

        return new ManagedId<X>(id, this);
    }

    /**
     * Returns the id of the entity from the resultset row.
     * 
     * @param session
     *            the session
     * @param row
     *            the row
     * @return the managedId or null
     * @throws SQLException
     *             if an SQL error occurrs
     * 
     * @since 2.0.0
     */
    public ManagedId<X> getId(SessionImpl session, ResultSet row) throws SQLException {
        return this.getId(session, row, this.getPrimaryTable().getIdFields());
    }

    /**
     * Returns the id of the entity from the resultset row.
     * 
     * @param session
     *            the session
     * @param row
     *            the row
     * @param idFields
     *            the id fields
     * @return the managedId or null
     * @throws SQLException
     *             if an SQL error occurrs
     * 
     * @since 2.0.0
     */
    public ManagedId<X> getId(SessionImpl session, ResultSet row, HashMap<AbstractColumn, String> idFields)
            throws SQLException {
        Object id;
        final MutableBoolean allNull = new MutableBoolean(true);

        if (this.hasSingleIdAttribute()) {
            id = this.getIdImpl(session, row, idFields, this.getIdMapping(), allNull);
        } else {
            // create the id class
            id = this.newCompositeId();

            for (final Pair<SingularMapping<?, ?>, AbstractAccessor> pair : this.getIdMappings()) {
                final SingularMapping<?, ?> child = pair.getFirst();

                final Object childId = this.getIdImpl(session, row, idFields, child, allNull);
                if (childId != null) {
                    allNull.setValue(false);
                }

                pair.getSecond().set(id, childId);
            }
        }

        if (allNull.booleanValue()) {
            return null;
        }

        return new ManagedId<X>(id, this);
    }

    private Object getIdImpl(Object instance, SingularMapping<?, ?> idMapping, MutableBoolean allNull) {
        // handle basic mapping
        if (idMapping instanceof BasicMappingImpl) {
            final Object value = idMapping.get(instance);
            if (value != null) {
                allNull.setValue(false);
            }

            return value;
        }

        // handle embedded id
        if (idMapping instanceof EmbeddedMappingImpl) {
            final EmbeddedMappingImpl<?, ?> embeddedMapping = (EmbeddedMappingImpl<?, ?>) idMapping;
            final Object id = embeddedMapping.getAttribute().newInstance();

            for (final Mapping<?, ?, ?> child : embeddedMapping.getChildren()) {
                final Object childId = this.getIdImpl(instance, (SingularMappingEx<?, ?>) child, allNull);
                if (childId != null) {
                    allNull.setValue(false);
                }

                ((AbstractMapping<?, ?, ?>) child).getAttribute().set(id, childId);
            }

            return allNull.booleanValue() ? null : id;
        }

        // handle singular associated
        final SingularAssociationMappingImpl<?, ?> singularAssociationMapping = (SingularAssociationMappingImpl<?, ?>) idMapping;

        final Object associate = idMapping.get(instance);
        final ManagedId<?> id = singularAssociationMapping.getType().getId(associate);

        return id != null ? id.getId() : null;
    }

    private Object getIdImpl(SessionImpl session, ResultSet row, HashMap<AbstractColumn, String> idFields,
            SingularMapping<?, ?> idMapping, MutableBoolean allNull) throws SQLException {

        // handle basic mapping
        if (idMapping instanceof BasicMappingImpl) {
            final BasicColumn column = ((BasicMappingImpl<?, ?>) idMapping).getColumn();
            final String field = idFields.get(column);

            final Object value = row.getObject(field);
            if (value != null) {
                allNull.setValue(false);
            }

            return value;
        }

        // handle embedded id
        if (idMapping instanceof EmbeddedMappingImpl) {
            final EmbeddedMappingImpl<?, ?> embeddedMapping = (EmbeddedMappingImpl<?, ?>) idMapping;
            final Object id = embeddedMapping.getAttribute().newInstance();

            for (final Mapping<?, ?, ?> child : embeddedMapping.getChildren()) {
                Object childId = this.getIdImpl(session, row, idFields, (SingularMappingEx<?, ?>) child, allNull);

                final AttributeImpl<?, ?> attribute = ((AbstractMapping<?, ?, ?>) child).getAttribute();
                final PersistentAttributeType attributeType = attribute.getPersistentAttributeType();
                if ((attributeType == PersistentAttributeType.MANY_TO_ONE) //
                        || (attributeType == PersistentAttributeType.ONE_TO_ONE)) {
                    childId = session.getEntityManager().getReference(attribute.getJavaType(), childId);
                }

                attribute.set(id, childId);
            }

            return allNull.booleanValue() ? null : id;
        }

        // handle singular associated
        final SingularAssociationMappingImpl<?, ?> singularAssociationMapping = (SingularAssociationMappingImpl<?, ?>) idMapping;

        final HashMap<AbstractColumn, String> translatedIdFields = Maps.newHashMap();
        for (final JoinColumn joinColumn : singularAssociationMapping.getForeignKey().getJoinColumns()) {
            translatedIdFields.put(joinColumn.getReferencedColumn(), idFields.get(joinColumn));
        }

        final ManagedId<?> id = singularAssociationMapping.getType().getId(session, row, translatedIdFields);

        return id != null ? id.getId() : null;
    }

    /**
     * Returns the single id mapping.
     * 
     * @return the single id mapping
     * 
     * @since 2.0.0
     */
    @Override
    public SingularMappingEx<? super X, ?> getIdMapping() {
        if (this.idMapping != null) {
            return this.idMapping;
        }

        synchronized (this) {
            if (this.idMapping != null) {
                return this.idMapping;
            }

            for (final Mapping<? super X, ?, ?> mapping : this.entityMapping.getChildren()) {
                if ((mapping instanceof SingularMappingEx)
                        && ((SingularMappingEx<? super X, ?>) mapping).getAttribute().isId()) {
                    this.idMapping = (SingularMappingEx<? super X, ?>) mapping;

                    return this.idMapping;
                }
            }

            throw new NullPointerException(); // impossible
        }
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    @SuppressWarnings("unchecked")
    public Pair<SingularMapping<?, ?>, AbstractAccessor>[] getIdMappings() {
        if (this.idMappings != null) {
            return this.idMappings;
        }

        // populate the id attributes with the inheritance
        synchronized (this) {
            if (this.idMappings != null) {
                return this.idMappings;
            }

            final List<Pair<SingularMapping<?, ?>, AbstractAccessor>> _idMappings = Lists.newArrayList();
            for (final Mapping<? super X, ?, ?> mapping : this.entityMapping.getChildren()) {
                // only interested in id mappings
                if (!(mapping instanceof SingularMappingEx)
                        || !((SingularMappingEx<? super X, ?>) mapping).getAttribute().isId()) {
                    continue;
                }

                // must have a corresponding field
                Field field;
                try {
                    field = this.getIdClass().getDeclaredField(mapping.getName());
                } catch (final Exception e) {
                    throw new MappingException(
                            "Attribute not found: " + this.getIdClass().getName() + "." + mapping.getName(),
                            mapping.getLocator());
                }

                final Class<?> javaType;
                if (mapping instanceof SingularAssociationMappingImpl) {
                    final EntityTypeImpl<?> type = ((SingularAssociationMappingImpl<? super X, ?>) mapping)
                            .getType();

                    if (type.hasSingleIdAttribute()) {
                        javaType = type.getIdType().getJavaType();
                    } else {
                        javaType = type.getIdClass();
                    }
                } else {
                    javaType = mapping.getJavaType();
                }

                if (field.getType() != javaType) {
                    throw new MappingException("Attribute types mismatch: " + field + ", " + mapping.getJavaType(),
                            mapping.getLocator());
                }

                final SingularMappingEx<? super X, ?> singularMapping = (SingularMappingEx<? super X, ?>) mapping;
                final AbstractAccessor accessor = ReflectHelper.getAccessor(field);

                _idMappings.add(new Pair<SingularMapping<?, ?>, AbstractAccessor>(singularMapping, accessor));
            }

            final Pair<SingularMapping<?, ?>, AbstractAccessor>[] idMappings0 = new Pair[_idMappings.size()];
            _idMappings.toArray(idMappings0);

            this.idMappings = idMappings0;
        }

        return this.idMappings;
    }

    /**
     * Returns the inheritance type of the entity.
     * 
     * @return the inheritance type of the entity or <code>null</code>
     * 
     * @since 2.0.0
     */
    public InheritanceType getInheritanceType() {
        return this.inheritanceType;
    }

    /**
     * Returns the id of the instance.
     * 
     * @param instance
     *            the instance
     * @return the id of the instance
     * 
     * @since 2.0.0
     */
    public Object getInstanceId(X instance) {
        return this.getIdMapping().get(instance);
    }

    /**
     * Returns the managed instance for the instance.
     * 
     * @param instance
     *            the instance to create managed instance for
     * @param session
     *            the session
     * @return managed id for the instance
     * @throws NullPointerException
     *             thrown if the instance is null
     * 
     * @since 2.0.0
     */
    public ManagedInstance<X> getManagedInstance(SessionImpl session, X instance) {
        if (instance == null) {
            throw new NullPointerException();
        }

        return new ManagedInstance<X>(this, session, instance);
    }

    /**
     * Creates a new managed instance with the id.
     * 
     * @param session
     *            the session
     * @param id
     *            the primary key
     * @param lazy
     *            if the instance is lazy
     * @return the managed instance created
     * 
     * @since 2.0.0
     */
    @SuppressWarnings({ "unchecked" })
    public ManagedInstance<X> getManagedInstanceById(SessionImpl session, ManagedId<X> id, boolean lazy) {
        try {
            final X instance = (X) this.constructor
                    .newInstance(new Object[] { this.getJavaType(), session, id.getId(), !lazy });

            final ManagedInstance<X> managedInstance = new ManagedInstance<X>(this, session, instance, id);

            ((EnhancedInstance) instance).__enhanced__$$__setManagedInstance(managedInstance);

            return managedInstance;
        } catch (final Exception e) {
            throw new PersistenceException("Cannot create instance " + id, e);
        } // not possible
    }

    /**
     * Returns the mapped id.
     * 
     * @param name
     *            thename of the id field
     * @param instance
     *            the instance
     * @return the id
     * 
     * @since 2.0.0
     */
    public Object getMappedId(String name, Object instance) {
        final AssociatedSingularAttribute<? super X, ?> attribute = this.idMap.get(name);
        if (attribute == null) {
            return null;
        }

        final Object mappedEntity = attribute.get(instance);
        if (mappedEntity == null) {
            return null;
        }

        final EntityTypeImpl<?> entity = this.getMetamodel().entity(mappedEntity.getClass());
        if (entity.hasSingleIdAttribute()) {
            return entity.getIdMapping().get(mappedEntity);
        }

        return null;
    }

    /**
     * Retuns the element collection mappings.
     * 
     * @return the element collection mappings
     * 
     * @since 2.0.0
     */
    public JoinedMapping<?, ?, ?>[] getMappingsJoined() {
        FinalWrapper<JoinedMapping<?, ?, ?>[]> wrapper = this.mappingsJoined;

        if (wrapper == null) {
            synchronized (this) {
                if (this.mappingsJoined == null) {

                    final List<JoinedMapping<?, ?, ?>> _mappingsJoined = Lists.newArrayList();
                    this.entityMapping.addJoinedMappings(_mappingsJoined);

                    final JoinedMapping<?, ?, ?>[] __mappingsJoined = new JoinedMapping[_mappingsJoined.size()];
                    _mappingsJoined.toArray(__mappingsJoined);

                    this.mappingsJoined = new FinalWrapper<JoinedMapping<?, ?, ?>[]>(__mappingsJoined);
                }

                wrapper = this.mappingsJoined;
            }
        }

        return wrapper.value;
    }

    /**
     * Retuns the element collection mappings.
     * 
     * @return the element collection mappings
     * 
     * @since 2.0.0
     */
    public PluralMappingEx<?, ?, ?>[] getMappingsPlural() {
        FinalWrapper<PluralMappingEx<?, ?, ?>[]> wrapper = this.mappingsPlural;

        if (wrapper == null) {
            synchronized (this) {
                if (this.mappingsPlural == null) {

                    final List<PluralMappingEx<?, ?, ?>> _mappingsPlural = Lists.newArrayList();
                    this.entityMapping.addPluralMappings(_mappingsPlural);

                    final PluralMappingEx<?, ?, ?>[] __mappingsPlural = new PluralMappingEx[_mappingsPlural.size()];
                    _mappingsPlural.toArray(__mappingsPlural);

                    this.mappingsPlural = new FinalWrapper<PluralMappingEx<?, ?, ?>[]>(__mappingsPlural);
                }

                wrapper = this.mappingsPlural;
            }
        }

        return wrapper.value;
    }

    /**
     * Returns the sorted plural associations.
     * 
     * @return the sorted plural associations
     * 
     * @since 2.0.0
     */
    public PluralMappingEx<?, ?, ?>[] getMappingsPluralSorted() {
        FinalWrapper<PluralMappingEx<?, ?, ?>[]> wrapper = this.mappingsPluralSorted;

        if (wrapper == null) {
            synchronized (this) {
                if (this.mappingsPluralSorted == null) {

                    final List<PluralMappingEx<?, ?, ?>> _mappingsPluralSorted = Lists.newArrayList();
                    for (final PluralMappingEx<?, ?, ?> mapping : this.getMappingsPlural()) {
                        if (mapping.getOrderBy() != null) {
                            _mappingsPluralSorted.add(mapping);
                        }
                    }

                    final PluralMappingEx<?, ?, ?>[] __mappingsPluralSorted = new PluralMappingEx[_mappingsPluralSorted
                            .size()];
                    _mappingsPluralSorted.toArray(__mappingsPluralSorted);

                    this.mappingsPluralSorted = new FinalWrapper<PluralMappingEx<?, ?, ?>[]>(
                            __mappingsPluralSorted);
                }

                wrapper = this.mappingsPluralSorted;
            }
        }

        return wrapper.value;
    }

    /**
     * Returns the singular mappings.
     * 
     * @return the singular mappings
     * 
     * @since 2.0.0
     */
    public AbstractMapping<?, ?, ?>[] getMappingsSingular() {
        FinalWrapper<AbstractMapping<?, ?, ?>[]> wrapper = this.singularMappings;

        if (wrapper == null) {
            synchronized (this) {
                if (this.singularMappings == null) {

                    final List<AbstractMapping<?, ?, ?>> _singularMappings = Lists.newArrayList();

                    this.entityMapping.addSingularMappings(_singularMappings);

                    final AbstractMapping<?, ?, ?>[] __singularMappings = new AbstractMapping[_singularMappings
                            .size()];
                    _singularMappings.toArray(__singularMappings);

                    this.singularMappings = new FinalWrapper<AbstractMapping<?, ?, ?>[]>(__singularMappings);
                }

                wrapper = this.singularMappings;
            }
        }

        return wrapper.value;
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    public String getName() {
        return this.name;
    }

    /**
     * Returns the parent of the entity.
     * 
     * @return the parent entity or <code>null</code>
     * 
     * @since 2.0.0
     */
    public EntityTypeImpl<? super X> getParent() {
        if (this.isRoot()) {
            return null;
        }

        return (EntityTypeImpl<? super X>) this.getSupertype();
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    public PersistenceType getPersistenceType() {
        return PersistenceType.ENTITY;
    }

    /**
     * Returns the primary table of the type
     * 
     * @return the primary table
     * 
     * @since 2.0.0
     */
    @Override
    public EntityTable getPrimaryTable() {
        return this.primaryTable;
    }

    /**
     * Returns the entityMapping of the EntityTypeImpl.
     * 
     * @return the entityMapping of the EntityTypeImpl
     * 
     * @since 2.0.0
     */
    public EntityMapping<X> getRootMapping() {
        return this.entityMapping;
    }

    /**
     * Returns the root type of the hierarchy.
     * 
     * @return the root type of the hierarchy
     * 
     * @since 2.0.0
     */
    public EntityTypeImpl<? super X> getRootType() {
        if (this.rootType != null) {
            return this.rootType;
        }

        EntityTypeImpl<? super X> supertype = this;

        while (supertype.getSupertype() instanceof EntityTypeImpl) {
            supertype = (EntityTypeImpl<? super X>) supertype.getSupertype();
        }

        this.rootType = supertype;

        return this.rootType;
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    public AbstractTable getTable(String tableName) {
        if (StringUtils.isBlank(tableName)) {
            return this.primaryTable;
        }

        return this.tableMap.get(tableName);
    }

    /**
     * Returns the tables of the type, starting from the top of the hierarchy.
     * 
     * @return the tables of the type
     * 
     * @since 2.0.0
     */
    public EntityTable[] getTables() {
        FinalWrapper<EntityTable[]> wrapper = this.tables;

        if (wrapper == null) {
            synchronized (this) {
                if (this.tables == null) {

                    final EntityTable[] _tables = new EntityTable[this.tableMap.size()];
                    this.tableMap.values().toArray(_tables);

                    Arrays.sort(_tables, new Comparator<EntityTable>() {

                        @Override
                        public int compare(EntityTable o1, EntityTable o2) {
                            if ((o1 instanceof SecondaryTable) && !(o2 instanceof SecondaryTable)) {
                                return 1;
                            }

                            if ((o2 instanceof SecondaryTable) && !(o1 instanceof SecondaryTable)) {
                                return -1;
                            }

                            return o1.getName().compareTo(o2.getName());
                        }
                    });

                    this.tables = new FinalWrapper<EntityTable[]>(_tables);
                }

                wrapper = this.tables;
            }
        }

        return wrapper.value;
    }

    /**
     * Initializes the custom indexes
     * 
     * @since 2.0.0
     */
    private void initCustomIndexes() {
        for (final IndexMetadata index : this.indexes) {
            final EntityTable table = StringUtils.isNotBlank(index.getTable()) ? this.tableMap.get(index.getTable())
                    : this.primaryTable;
            if (table == null) {
                throw new MappingException("Cannot locate table for index " + index.getName(), index.getLocator());
            }

            final List<BasicColumn> columns = Lists.newArrayList();
            for (final String path : index.getColumnNames()) {
                final AbstractMapping<?, ?, ?> mapping = this.getRootMapping().getMapping(path);
                if (!(mapping instanceof BasicMappingImpl)) {
                    throw new MappingException(
                            "Cannot locate the basic path " + path + " for index " + index.getName(),
                            index.getLocator());
                }

                columns.add(((BasicMappingImpl<?, ?>) mapping).getColumn());
            }

            table.addIndex(index.getName(), columns.toArray(new BasicColumn[columns.size()]));
        }
    }

    /**
     * Initializes the indexes
     * 
     * @since 2.0.0
     */
    private void initIndexes() {
        for (final BasicMappingImpl<?, ?> basicMapping : this.getBasicMappings()) {
            final IndexMetadata index = basicMapping.getAttribute().getIndex();
            if (index != null) {
                final EntityTable table = StringUtils.isNotBlank(index.getTable())
                        ? this.tableMap.get(index.getTable())
                        : this.primaryTable;
                if (table == null) {
                    throw new MappingException("Cannot locate table for index " + index.getName(),
                            index.getLocator());
                }

                if (table.addIndex(index.getName(), basicMapping.getColumn())) {
                    throw new MappingException("Duplicate index with the same name " + index.getName(),
                            index.getLocator());
                }
            }
        }

        this.initCustomIndexes();
    }

    /**
     * Initializes the tables.
     * 
     * @since 2.0.0
     * @param metadata
     */
    private void initTables(EntityMetadata metadata) {
        if (this.getRootType() != this) {
            if (this.getRootType().getInheritanceType() == null) {
                this.getRootType().setInherited();
            }

            switch (this.getRootType().getInheritanceType()) {
            case SINGLE_TABLE:
                // if this is the root, create the primary table
                if (this.getRootType() == this) {
                    this.primaryTable = new EntityTable(this.getMetamodel().getJdbcAdaptor(), this,
                            metadata.getTable());

                    this.tableMap.put(this.primaryTable.getName(), this.primaryTable);
                }
                // else map the primary key to the root type's primary table and the tables from the parent
                else {
                    final EntityTypeImpl<? super X> supertype = (EntityTypeImpl<? super X>) this.getSupertype();

                    this.primaryTable = supertype.primaryTable;
                    this.tableMap.putAll(supertype.tableMap);
                }
                break;
            case JOINED:
                // if this is the root, create the primary table
                if (this.getRootType() == this) {
                    this.primaryTable = new EntityTable(this.getMetamodel().getJdbcAdaptor(), this,
                            metadata.getTable());

                    this.tableMap.put(this.primaryTable.getName(), this.primaryTable);
                }
                // else map all the parent tables and create the primary table as secondary table
                else {
                    final EntityTypeImpl<? super X> supertype = (EntityTypeImpl<? super X>) this.getSupertype();
                    this.tableMap.putAll(supertype.tableMap);

                    this.primaryTable = new SecondaryTable(this.getMetamodel().getJdbcAdaptor(), this,
                            metadata.getTable());
                    this.tableMap.put(this.primaryTable.getName(), this.primaryTable);
                }
                break;
            case TABLE_PER_CLASS:
                throw new MappingException("TABLE_PER_CLASS inheritence type is not yet supported",
                        this.getRootType().getLocator());
            }
        }
        // create the primary table
        else {
            this.primaryTable = new EntityTable(this.getMetamodel().getJdbcAdaptor(), this, metadata.getTable());

            this.tableMap.put(this.primaryTable.getName(), this.primaryTable);
        }

        for (final SecondaryTableMetadata secondaryTableMetadata : metadata.getSecondaryTables()) {
            final SecondaryTable secondaryTable = new SecondaryTable(this.getMetamodel().getJdbcAdaptor(), this,
                    secondaryTableMetadata);
            this.tableMap.put(secondaryTableMetadata.getName(), secondaryTable);
        }
    }

    /**
     * Returns if the method is an id method.
     * 
     * @param method
     *            the method
     * @return if the method is an id method
     * 
     * @since 2.0.0
     */
    public boolean isIdMethod(Method method) {
        if (this.idMethods.containsKey(method)) { // if known id method, let go
            return true;
        }

        final String methodName = method.getName();
        if (methodName.startsWith("get") && (methodName.length() > 3)) { // check if id method
            for (final SingularAttribute<? super X, ?> attribute : this.getSingularAttributes()) {
                final String getterName = "get" + StringUtils.capitalize(attribute.getName());
                if (attribute.isId() && getterName.equals(method.getName())) {
                    this.idMethods.put(method, method);
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Returns if the entity is the root of the hierarchy.
     * 
     * @return true if the entity is the root of the hierarchy, false otherwise
     * 
     * @since 2.0.0
     */
    public boolean isRoot() {
        return this.getRootType() == this;
    }

    /**
     * Returns if the entity is suitable for batch insert, that is not of {@link IdType#IDENTITY}.
     * 
     * @return true if the entity is suitable for batch insert, false otherwise
     * 
     * @since 2.0.0
     */
    public boolean isSuitableForBatchInsert() {
        if (this.suitableForBatchInsert != null) {
            return this.suitableForBatchInsert;
        }

        return this.suitableForBatchInsert = this.hasSingleIdAttribute()
                && (this.idMapping instanceof BasicMappingImpl)
                && (((BasicMappingImpl<? super X, ?>) this.idMapping).getAttribute()
                        .getIdType() != IdType.IDENTITY);
    }

    /**
     * Links the entity's attribute mappings.
     * 
     * @since 2.0.0
     */
    private void linkMappings() {
        if (this.getRootType().getInheritanceType() != null) {
            // register the discriminator value
            IdentifiableTypeImpl<? super X> parent = this;
            do {
                ((EntityTypeImpl<? super X>) parent).children.put(this.discriminatorValue, this);

                parent = parent.getSupertype();
            } while (parent instanceof EntityTypeImpl);
        }

        // if the root type then create the discriminator column
        if ((this.getRootType() == this) && (this.inheritanceType != null)) {
            this.discriminatorColumn = new DiscriminatorColumn(this.getMetamodel().getJdbcAdaptor(),
                    this.primaryTable, this.metadata.getDiscriminatorColumn());
        }

        this.entityMapping.createMappings();

        // link the secondary tables
        for (final EntityTable table : this.tableMap.values()) {
            if (table instanceof SecondaryTable) {
                ((SecondaryTable) table).link();
            }
        }

        this.canBatchRemoves = (this.getVersionAttribute() == null) && this.hasSingleIdAttribute()
                && (this.getIdMapping() instanceof BasicAttribute);
    }

    /**
     * Performs inserts to each table for the managed instance.
     * 
     * @param connection
     *            the connection to use
     * @param managedInstances
     *            the managed instances to perform insert for
     * @param size
     *            the size of the batch
     * @throws SQLException
     *             thrown in case of an SQL Error
     * 
     * @since 2.0.0
     */
    public void performInsert(Connection connection, ManagedInstance<?>[] managedInstances, int size)
            throws SQLException {
        final Object[] instances = new Object[size];
        for (int i = 0; i < size; i++) {
            instances[i] = managedInstances[i].getInstance();
        }

        for (final EntityTable table : this.getTables()) {
            table.performInsert(connection, this, instances, size);
        }

        for (int i = 0; i < size; i++) {
            managedInstances[i].setStatus(Status.MANAGED);
        }
    }

    /**
     * Performs refresh for the instance
     * 
     * @param connection
     *            the connection
     * @param instance
     *            the managed instance
     * @param lockMode
     *            the lock mode
     * @param processed
     *            the set of processed instances
     * 
     * @since 2.0.0
     */
    public void performRefresh(Connection connection, ManagedInstance<X> instance, LockModeType lockMode,
            Set<Object> processed) {
        final SessionImpl session = instance.getSession();

        final QueryImpl<X> q = session.getEntityManager().createQuery(this.getCriteriaRefresh());

        if (processed.size() == 0) {
            q.setLockMode(lockMode);
        }

        final Object id = instance.getId().getId();

        // if has single id then pass it on
        if (this.hasSingleIdAttribute()) {
            q.setParameter(1, id);
        } else {
            int i = 1;
            for (final Pair<SingularMapping<?, ?>, AbstractAccessor> pair : this.getIdMappings()) {
                q.setParameter(i++, pair.getSecond().get(id));
            }
        }

        instance.setRefreshing(true);

        try {
            q.getSingleResult();
        } finally {
            instance.setRefreshing(false);
        }
    }

    /**
     * @param connection
     *            the connection to use
     * @param managedInstances
     *            the managed instance to perform remove for
     * @param size
     *            the size of the batch
     * @throws SQLException
     *             thrown in case of an SQL Error
     * 
     * @since 2.0.0
     */
    public void performRemove(Connection connection, ManagedInstance<?>[] managedInstances, int size)
            throws SQLException {
        final Object[] instances = new Object[size];
        for (int i = 0; i < size; i++) {
            instances[i] = managedInstances[i].getInstance();
        }

        for (final EntityTable table : this.getTables()) {
            if (table == this.primaryTable) {
                continue;
            }

            table.performRemove(connection, instances, size);
        }

        this.primaryTable.performRemove(connection, instances, size);
    }

    /**
     * Performs select to find the instance.
     * 
     * @param entityManager
     *            the entity manager to use
     * @param id
     *            the id of the instance to select
     * @param lockMode
     *            the lock mode
     * @return the instance found or null
     * 
     * @since 2.0.0
     */
    public X performSelect(EntityManagerImpl entityManager, Object id, LockModeType lockMode) {
        final QueryImpl<X> q = entityManager.createQuery(this.getCriteriaSelect());

        q.setLockMode(lockMode);

        // if has single id then pass it on
        if (this.hasSingleIdAttribute()) {
            q.setParameter(1, id);
        } else {
            int i = 1;
            for (final Pair<SingularMapping<?, ?>, AbstractAccessor> pair : this.getIdMappings()) {
                q.setParameter(i++, pair.getSecond().get(id));
            }
        }

        return q.getSingleResult();
    }

    /**
     * Performs the update for the instance.
     * 
     * @param connection
     *            the connection to use
     * @param managedInstance
     *            the managed instance to perform update for
     * @throws SQLException
     *             thrown in case of an SQL Error
     * 
     * @since 2.0.0
     */
    public void performUpdate(Connection connection, ManagedInstance<?> managedInstance) throws SQLException {
        FinalWrapper<EntityTable[]> wrapper = this.updateTables;

        final Object instance = managedInstance.getInstance();
        final Object oldVersion = managedInstance.getOldVersion();

        if (wrapper == null) {
            synchronized (this) {
                if (this.updateTables == null) {
                    final List<EntityTable> _updateTables = Lists.newArrayList(this.getTables());
                    for (final Iterator<EntityTable> i = _updateTables.iterator(); i.hasNext();) {
                        if (!i.next().performUpdateWithUpdatability(connection, this, managedInstance.getInstance(),
                                oldVersion)) {
                            i.remove();
                        }
                    }

                    this.updateTables = new FinalWrapper<EntityTable[]>(
                            _updateTables.toArray(new EntityTable[_updateTables.size()]));
                }

                wrapper = this.updateTables;
            }
        } else {
            for (final EntityTable table : wrapper.value) {
                table.performUpdate(connection, this, instance, oldVersion);
            }
        }
    }

    /**
     * Performs the version update for the instance.
     * 
     * @param connection
     *            the connection to use
     * @param instance
     *            the managed instance to perform update for
     * @param oldVersion
     *            the old version value
     * @param newVersion
     *            the new version value
     * @throws SQLException
     *             thrown in case of an SQL Error
     * 
     * @since 2.0.0
     */
    public void performVersionUpdate(Connection connection, ManagedInstance<? extends X> instance,
            Object oldVersion, Object newVersion) throws SQLException {
        this.getTables()[0].performVersionUpdate(connection, instance.getInstance(), oldVersion, newVersion);
    }

    /**
     * Prepares the dependencies for the associate.
     * 
     * @param associate
     *            the associate
     * 
     * @since 2.0.0
     */
    public void prepareDependenciesFor(EntityTypeImpl<?> associate) {
        // prepare the related associations
        final Set<AssociationMappingImpl<?, ?, ?>> attributes = Sets.newHashSet();

        for (final AssociationMappingImpl<?, ?, ?> association : this.getAssociations()) {

            // only owner associations impose priority
            if (!association.isOwner()) {
                continue;
            }

            // only relations kept in the row impose priority
            if ((association.getAttribute().getPersistentAttributeType() != PersistentAttributeType.ONE_TO_ONE) && //
                    (association.getAttribute()
                            .getPersistentAttributeType() != PersistentAttributeType.MANY_TO_ONE)) {
                continue;
            }

            final Class<?> javaType = association.getAttribute().getJavaType();

            if (javaType.isAssignableFrom(associate.getBindableJavaType())) {
                attributes.add(association);
            }
        }

        final AssociationMappingImpl<?, ?, ?>[] dependencies = new AssociationMappingImpl[attributes.size()];
        attributes.toArray(dependencies);

        this.dependencyCount += dependencies.length;

        this.dependencyMap.put(associate, dependencies);
    }

    /**
     * @param r
     *            the fetch parent
     * @param depth
     *            the depth
     * @param parent
     *            the parent
     * 
     * @since 2.0.0
     */
    public void prepareEagerJoins(FetchParent<?, ?> r, int depth, AssociationMappingImpl<?, ?, ?> parent) {
        if (depth < this.maxFetchJoinDepth) {
            this.prepareEagerJoins(r, depth, parent, this.entityMapping.getEagerMappings());
        }
    }

    private void prepareEagerJoins(FetchParent<?, ?> r, int depth, AssociationMappingImpl<?, ?, ?> parent,
            JoinedMapping<?, ?, ?>[] mappings) {
        for (final JoinedMapping<?, ?, ?> mapping : mappings) {
            // Element collection
            if (mapping.getMappingType() == MappingType.ELEMENT_COLLECTION) {
                r.fetch(mapping.getAttribute().getName(), JoinType.LEFT);
                continue;
            }
            // embeddable
            else if (mapping.getMappingType() == MappingType.EMBEDDABLE) {
                final Fetch<?, Object> r2 = r.fetch(mapping.getAttribute().getName(), JoinType.LEFT);

                this.prepareEagerJoins(r2, depth, parent, ((EmbeddedMappingImpl<?, ?>) mapping).getEagerMappings());

                continue;
            }
            // association
            else {
                final AssociationMappingImpl<?, ?, ?> association = (AssociationMappingImpl<?, ?, ?>) mapping;
                // if we are coming from the inverse side and inverse side is not many-to-one then skip
                if ((parent != null) && //
                        (association.getInverse() == parent) && //
                        (parent.getAttribute()
                                .getPersistentAttributeType() != PersistentAttributeType.MANY_TO_ONE)) {
                    continue;
                }

                // check association's fetch strategy and max depth
                if ((association.getMaxFetchJoinDepth() < depth)
                        || (association.getFetchStrategy() == FetchStrategyType.SELECT)) {
                    continue;
                }

                final Fetch<?, Object> r2 = r.fetch(((AbstractMapping<?, ?, ?>) mapping).getAttribute().getName(),
                        JoinType.LEFT);
                final EntityTypeImpl<?> type = association.getType();
                type.prepareEagerJoins(r2, depth + 1, association);
            }
        }
    }

    /**
     * Runs the validators for the instance.
     * 
     * @param entityManagerFactory
     *            the entity manager factory
     * @param instance
     *            the instance
     * @return the set of validation errors
     * 
     * @since 2.0.0
     */
    public Set<ConstraintViolation<Object>> runValidators(EntityManagerFactoryImpl entityManagerFactory,
            ManagedInstance<?> instance) {
        final ValidatorFactory factory = entityManagerFactory.getValidationFactory();

        final Validator validator = factory.usingContext().getValidator();

        Class<?>[] groups;

        switch (instance.getStatus()) {
        case NEW:
            groups = entityManagerFactory.getPersistValidators();
            break;
        case MANAGED:
            groups = entityManagerFactory.getUpdateValidators();
            break;
        default:
            groups = entityManagerFactory.getRemoveValidators();
            break;
        }

        return validator.validate((Object) instance.getInstance(), groups);
    }

    /**
     * Sets the id of the entity from the instance.
     * 
     * @param session
     *            the session
     * @param instance
     *            the instance
     * @param id
     *            the id
     * 
     * @since 2.0.0
     */
    public void setId(SessionImpl session, Object instance, Object id) {
        if (this.hasSingleIdAttribute()) {
            this.setIdImpl(session, instance, id, this.getIdMapping());
        } else {
            for (final Pair<SingularMapping<?, ?>, AbstractAccessor> pair : this.getIdMappings()) {
                final SingularMapping<?, ?> child = pair.getFirst();
                final AbstractAccessor accessor = pair.getSecond();

                final Object childId = id != null ? accessor.get(id) : null;

                this.setIdImpl(session, instance, childId, child);
            }
        }
    }

    private void setIdImpl(SessionImpl session, Object instance, Object id, SingularMapping<?, ?> idMapping) {
        // handle basic mapping
        if (idMapping instanceof BasicMappingImpl) {
            idMapping.set(instance, id);
        }

        // handle embedded id
        else if (idMapping instanceof EmbeddedMappingImpl) {
            final EmbeddedMappingImpl<?, ?> embeddedMapping = (EmbeddedMappingImpl<?, ?>) idMapping;

            if (id != null) {
                for (final Mapping<?, ?, ?> child : embeddedMapping.getChildren()) {
                    final Object childId = ((AbstractMapping<?, ?, ?>) child).getAttribute().get(id);

                    this.setIdImpl(session, instance, childId, (SingularMappingEx<?, ?>) child);
                }
            }
        }

        // handle singular associated
        else {
            final SingularAssociationMappingImpl<?, ?> singularAssociationMapping = (SingularAssociationMappingImpl<?, ?>) idMapping;
            final EntityTypeImpl<?> type = singularAssociationMapping.getType();

            if ((id == null) || type.getJavaType().isAssignableFrom(id.getClass())) {
                idMapping.set(instance, id);
            } else {
                idMapping.set(instance, session.getEntityManager().getReference(type.getJavaType(), id));
            }
        }
    }

    private synchronized void setInherited() {
        if (this.inheritanceType == null) {
            this.inheritanceType = InheritanceType.SINGLE_TABLE;

            if (this.discriminatorColumn == null) {
                this.discriminatorColumn = new DiscriminatorColumn(this.getMetamodel().getJdbcAdaptor(),
                        this.primaryTable, this.metadata.getDiscriminatorColumn());
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    public String toString() {
        return "EntityTypeImpl [name=" + this.name + "]";
    }
}