/*******************************************************************************
 * Copyright (c) 1998, 2014 Oracle and/or its affiliates, IBM Corporation. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     05/16/2008-1.0M8 Guy Pelletier 
 *       - 218084: Implement metadata merging functionality between mapping files
 *     06/20/2008-1.0 Guy Pelletier 
 *       - 232975: Failure when attribute type is generic
 *     08/27/2008-1.1 Guy Pelletier 
 *       - 211329: Add sequencing on non-id attribute(s) support to the EclipseLink-ORM.XML Schema
 *     09/23/2008-1.1 Guy Pelletier 
 *       - 241651: JPA 2.0 Access Type support
 *     01/28/2009-2.0 Guy Pelletier 
 *       - 248293: JPA 2.0 Element Collections (part 1)
 *     02/06/2009-2.0 Guy Pelletier 
 *       - 248293: JPA 2.0 Element Collections (part 2)
 *     02/25/2009-2.0 Guy Pelletier 
 *       - 265359: JPA 2.0 Element Collections - Metadata processing portions
 *     03/27/2009-2.0 Guy Pelletier 
 *       - 241413: JPA 2.0 Add EclipseLink support for Map type attributes
 *     04/03/2009-2.0 Guy Pelletier
 *       - 241413: JPA 2.0 Add EclipseLink support for Map type attributes
 *     04/24/2009-2.0 Guy Pelletier 
 *       - 270011: JPA 2.0 MappedById support
 *     06/02/2009-2.0 Guy Pelletier 
 *       - 278768: JPA 2.0 Association Override Join Table
 *     06/25/2009-2.0 Michael O'Brien 
 *       - 266912: change MappedSuperclass handling in stage2 to pre process accessors
 *          in support of the custom descriptors holding mappings required by the Metamodel. 
 *          We handle undefined parameterized generic types for a MappedSuperclass defined
 *          Map field by returning Void in this case.
 *     09/29/2009-2.0 Guy Pelletier 
 *       - 282553: JPA 2.0 JoinTable support for OneToOne and ManyToOne
 *     10/21/2009-2.0 Guy Pelletier 
 *       - 290567: mappedbyid support incomplete
 *     11/06/2009-2.0 Guy Pelletier 
 *       - 286317: UniqueConstraint xml element is changing (plus couple other fixes, see bug)
 *     01/22/2010-2.0.1 Guy Pelletier 
 *       - 294361: incorrect generated table for element collection attribute overrides
 *     01/26/2010-2.0.1 Guy Pelletier 
 *       - 299893: @MapKeyClass does not work with ElementCollection
 *     03/08/2010-2.1 Guy Pelletier 
 *       - 303632: Add attribute-type for mapping attributes to EclipseLink-ORM
 *     03/29/2010-2.1 Guy Pelletier 
 *       - 267217: Add Named Access Type to EclipseLink-ORM
 *     04/09/2010-2.1 Guy Pelletier 
 *       - 307050: Add defaults for access methods of a VIRTUAL access type
 *     04/27/2010-2.1 Guy Pelletier 
 *       - 309856: MappedSuperclasses from XML are not being initialized properly
 *     05/19/2010-2.1 Guy Pelletier 
 *       - 313574: Lower case primary key column association does not work when upper casing flag is set to true
 *     06/14/2010-2.2 Guy Pelletier 
 *       - 264417: Table generation is incorrect for JoinTables in AssociationOverrides
 *     07/05/2010-2.1.1 Guy Pelletier 
 *       - 317708: Exception thrown when using LAZY fetch on VIRTUAL mapping
 *     08/20/2010-2.2 Guy Pelletier 
 *       - 323252: Canonical model generator throws NPE on virtual 1-1 or M-1 mapping
 *     09/03/2010-2.2 Guy Pelletier 
 *       - 317286: DB column lenght not in sync between @Column and @JoinColumn
 *     10/27/2010-2.2 Guy Pelletier 
 *       - 328114: @AttributeOverride does not work with nested embeddables having attributes of the same name
 *     12/01/2010-2.2 Guy Pelletier 
 *       - 331234: xml-mapping-metadata-complete overriden by metadata-complete specification 
 *     03/24/2011-2.3 Guy Pelletier 
 *       - 337323: Multi-tenant with shared schema support (part 1)
 *     11/10/2011-2.4 Guy Pelletier 
 *       - 357474: Address primaryKey option from tenant discriminator column
 *      *     30/05/2012-2.4 Guy Pelletier    
 *       - 354678: Temp classloader is still being used during metadata processing
 *     10/09/2012-2.5 Guy Pelletier 
 *       - 374688: JPA 2.1 Converter support
 *     10/25/2012-2.5 Guy Pelletier 
 *       - 374688: JPA 2.1 Converter support
 *     10/30/2012-2.5 Guy Pelletier 
 *       - 374688: JPA 2.1 Converter support
 *     11/19/2012-2.5 Guy Pelletier 
 *       - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support)
 *     11/28/2012-2.5 Guy Pelletier 
 *       - 374688: JPA 2.1 Converter support
 *     12/07/2012-2.5 Guy Pelletier 
 *       - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support)
 *     02/20/2013-2.5 Guy Pelletier 
 *       - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support)
 *     07/16/2013-2.5.1 Guy Pelletier 
 *       - 412384: Applying Converter for parameterized basic-type for joda-time's DateTime does not work
 *     06/20/2014-2.5.2 Rick Curtis 
 *       - 437760: AttributeOverride with no column name defined doesn't work.       
 *     07/01/2014-2.5.3 Rick Curtis 
 *       - 375101: Date and Calendar should not require @Temporal.
 ******************************************************************************/
package org.eclipse.persistence.internal.jpa.metadata.accessors.mappings;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.JoinFetchType;
import org.eclipse.persistence.annotations.Properties;
import org.eclipse.persistence.annotations.Property;
import org.eclipse.persistence.annotations.ReturnInsert;
import org.eclipse.persistence.annotations.ReturnUpdate;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.descriptors.VirtualAttributeAccessor;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.indirection.TransparentIndirectionPolicy;
import org.eclipse.persistence.internal.indirection.WeavedObjectBasicIndirectionPolicy;
import org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor;
import org.eclipse.persistence.internal.jpa.metadata.MetadataHelper;
import org.eclipse.persistence.internal.jpa.metadata.MetadataLogger;
import org.eclipse.persistence.internal.jpa.metadata.accessors.MetadataAccessor;
import org.eclipse.persistence.internal.jpa.metadata.accessors.PropertyMetadata;
import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor;
import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EmbeddableAccessor;
import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EntityAccessor;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAccessibleObject;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAnnotation;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataClass;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataMethod;
import org.eclipse.persistence.internal.jpa.metadata.columns.AssociationOverrideMetadata;
import org.eclipse.persistence.internal.jpa.metadata.columns.AttributeOverrideMetadata;
import org.eclipse.persistence.internal.jpa.metadata.columns.ColumnMetadata;
import org.eclipse.persistence.internal.jpa.metadata.columns.ForeignKeyMetadata;
import org.eclipse.persistence.internal.jpa.metadata.columns.JoinColumnMetadata;
import org.eclipse.persistence.internal.jpa.metadata.converters.AbstractConverterMetadata;
import org.eclipse.persistence.internal.jpa.metadata.converters.ClassInstanceMetadata;
import org.eclipse.persistence.internal.jpa.metadata.converters.ConvertMetadata;
import org.eclipse.persistence.internal.jpa.metadata.converters.EnumeratedMetadata;
import org.eclipse.persistence.internal.jpa.metadata.converters.JSONMetadata;
import org.eclipse.persistence.internal.jpa.metadata.converters.KryoMetadata;
import org.eclipse.persistence.internal.jpa.metadata.converters.LobMetadata;
import org.eclipse.persistence.internal.jpa.metadata.converters.SerializedMetadata;
import org.eclipse.persistence.internal.jpa.metadata.converters.TemporalMetadata;
import org.eclipse.persistence.internal.jpa.metadata.converters.XMLMetadata;
import org.eclipse.persistence.internal.jpa.metadata.mappings.MapKeyMetadata;
import org.eclipse.persistence.internal.jpa.metadata.xml.XMLEntityMappings;
import org.eclipse.persistence.internal.mappings.converters.AttributeNamePrefix;
import org.eclipse.persistence.internal.mappings.converters.AttributeNameTokenizer;
import org.eclipse.persistence.internal.queries.CollectionContainerPolicy;
import org.eclipse.persistence.internal.queries.MappedKeyMapContainerPolicy;
import org.eclipse.persistence.mappings.AggregateObjectMapping;
import org.eclipse.persistence.mappings.CollectionMapping;
import org.eclipse.persistence.mappings.ContainerMapping;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.DirectMapMapping;
import org.eclipse.persistence.mappings.DirectToFieldMapping;
import org.eclipse.persistence.mappings.EmbeddableMapping;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
import org.eclipse.persistence.mappings.OneToOneMapping;
import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping;
import org.eclipse.persistence.mappings.foundation.MapComponentMapping;
import org.eclipse.persistence.mappings.foundation.MapKeyMapping;

import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.EL_ACCESS_VIRTUAL;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_ACCESS_FIELD;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_ACCESS_PROPERTY;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_CONVERT;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_CONVERTS;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_FETCH_EAGER;

/**
 * INTERNAL:
 * An abstract mapping accessor. Holds common metadata for all mappings.
 * 
 * Key notes:
 * - any metadata mapped from XML to this class must be compared in the
 *   equals method.
 * - any metadata mapped from XML to this class must be handled in the merge
 *   method. (merging is done at the accessor/mapping level)
 * - any metadata mapped from XML to this class must be initialized in the
 *   initXMLObject  method.
 * - methods should be preserved in alphabetical order.
 * 
 * @author Guy Pelletier
 * @since EclipseLink 1.0
 */
public abstract class MappingAccessor extends MetadataAccessor {

    // Used for looking up attribute overrides for a map accessor.
    /** Dot notation key prefix. */
    protected static final String KEY_DOT_NOTATION
            = AttributeNamePrefix.KEY.getName() + AttributeNameTokenizer.SEPARATOR;
    /** Dot notation value prefix. */
    protected static final String VALUE_DOT_NOTATION
            = AttributeNamePrefix.VALUE.getName() + AttributeNameTokenizer.SEPARATOR;

    private final static String DEFAULT_MAP_KEY_COLUMN_SUFFIX = "_KEY";

    private ClassAccessor m_classAccessor;
    private DatabaseMapping m_mapping;
    private DatabaseMapping m_overrideMapping;
    private Map<String, PropertyMetadata> m_properties = new HashMap<String, PropertyMetadata>();
    private String m_attributeType;
    protected ColumnMetadata m_field;
    
    /**
     * INTERNAL:
     */
    protected MappingAccessor(MetadataAnnotation annotation, MetadataAccessibleObject accessibleObject, ClassAccessor classAccessor) {
        super(annotation, accessibleObject, classAccessor.getDescriptor(), classAccessor.getProject());
        
        // We must keep a reference to the class accessors where this
        // mapping accessor is defined. We need it to determine access types.
        m_classAccessor = classAccessor;
        
        // Once the class accessor is initialized, we can look for an explicit 
        // access type specification.
        initAccess();

        // Any mapping type may have a field for EIS/NoSQL data.
        if (isAnnotationPresent("org.eclipse.persistence.nosql.annotations.Field")) {
            m_field = new ColumnMetadata(getAnnotation("org.eclipse.persistence.nosql.annotations.Field"), this);
        }
        
        // Set the converts if some are present. 
        // Process all the converts first.
        if (isAnnotationPresent(JPA_CONVERTS)) {
            for (Object convert : getAnnotation(JPA_CONVERTS).getAttributeArray("value")) {
                addConvertMetadata(new ConvertMetadata((MetadataAnnotation) convert, this));
            }
        }
        
        // Process the single convert second.
        if (isAnnotationPresent(JPA_CONVERT)) {
            addConvertMetadata(new ConvertMetadata(getAnnotation(JPA_CONVERT), this));
        }
    }
    
    /**
     * INTERNAL:
     */
    protected MappingAccessor(String xmlElement) {
        super(xmlElement);
    }
    
    /**
     * INTERNAL:
     * Subclasses that support converts need to override this method otherwise
     * an exception will be thrown from those accessors that do not support them
     * when a user has defined them on that accessor.
     */
    protected void addConvert(ConvertMetadata convert) {
        throw ValidationException.invalidMappingForConvert(getJavaClassName(), getAttributeName());
    }
    
    /**
     * INTERNAL:
     * Add a JPA convert annotation to the converts list. If it is a map key 
     * convert, pass it on to the map key converts list.
     */
    protected void addConvertMetadata(ConvertMetadata convert) {
        if (convert.isForMapKey()) {
            // This isForMapKey call will remove the key prefix when there is one.
            addMapKeyConvert(convert);
        } else {
            addConvert(convert);
        }
    }
    
    /**
     * INTERNAL:
     * Process an attribute override for either an embedded object mapping, or
     * an element collection mapping containing embeddable objects.
     */
    protected void addFieldNameTranslation(EmbeddableMapping embeddableMapping, String overrideName, DatabaseField overrideField, MappingAccessor aggregatesAccessor) {
        DatabaseMapping aggregatesMapping = aggregatesAccessor.getMapping();
        DatabaseField aggregatesMappingField = aggregatesMapping.getField();
        
        // If we are specifying an attribute override to an id field that is
        // within the embeddable we must update the primary key field on the
        // owning descriptor.
        if (aggregatesAccessor.isId()) {
            updatePrimaryKeyField(aggregatesAccessor, overrideField);
        }
        
        if (overrideName.indexOf(".") > -1) {
            // Set the nested field name translation on the mapping. Nested 
            // (dot notation) overrides are initialized slightly different then
            // core field name translations which are based on column names. In
            // JPA we need to rely and differentiate based on the override
            // (attribute) name.
            embeddableMapping.addNestedFieldTranslation(overrideName, overrideField, aggregatesMappingField.getName());
        } else {
            // Set the field name translation on the mapping.
            embeddableMapping.addFieldTranslation(overrideField, aggregatesMappingField.getName());
        }
    }
    
    /**
     * INTERNAL:
     * Subclasses that support converts need to override this method otherwise
     * an exception will be thrown from those accessors that do not support them
     * when a user has defined them on that accessor.
     */
    protected void addMapKeyConvert(ConvertMetadata convert) {
        throw ValidationException.invalidMappingForMapKeyConvert(getJavaClassName(), getAttributeName());
    }
    
    /**
     * INTERNAL:
     * Return true is this accessor is a derived id accessor.
     * @see ObjectAccessor
     */
    public boolean derivesId() {
        return false;
    }
    
    /**
     * INTERNAL:
     * For merging and overriding to work properly, all ORMetadata must be able 
     * to compare themselves for metadata equality.
     */
    @Override
    public boolean equals(Object objectToCompare) {
        if (super.equals(objectToCompare) && objectToCompare instanceof MappingAccessor) {
            MappingAccessor mappingAccessor = (MappingAccessor) objectToCompare;
            
            // For extra safety compare that the owning class accessors of these
            // mapping accessors are the same.
            if (! valuesMatch(getClassAccessor(), mappingAccessor.getClassAccessor())) {
                return false;
            }

            if (! valuesMatch(m_field, mappingAccessor.getField())) {
                return false;
            }
            
            return valuesMatch(getAttributeType(), mappingAccessor.getAttributeType());
        }
        
        return false;
    }
    
    /**
     * INTERNAL:
     * Return the annotation if it exists.
     */
    @Override
    protected MetadataAnnotation getAnnotation(String annotation) {
        return getAccessibleObject().getAnnotation(annotation, getClassAccessor());
    }
    
    /**
     * INTERNAL:
     * Process the list of association overrides into a map, merging and 
     * overriding any association overrides where necessary with descriptor
     * level association overrides.
     */
    protected Map<String, AssociationOverrideMetadata> getAssociationOverrides(List<AssociationOverrideMetadata> associationOverrides) {
        // TODO: Be nice to look for duplicates within the same list.
        Map<String, AssociationOverrideMetadata> associationOverridesMap = new HashMap<String, AssociationOverrideMetadata>();
        
        for (AssociationOverrideMetadata associationOverride : associationOverrides) {
            String name = associationOverride.getName();
            
            // An association override from a sub-entity class will name its
            // association override slightly different in that it will have 
            // one extra dot notation at the front. E.G. A mapped superclass 
            // that defines an embedded attribute named 'record' can define 
            // association overrides directly on the mapping, that is, 
            // 'date'. Whereas from an entity class to override 'date' on 
            // 'record', the attribute name will be 'record.date'
            String dotNotationName = getAttributeName() + "." + name;
            if (getClassAccessor().isMappedSuperclass() && getDescriptor().hasAssociationOverrideFor(dotNotationName)) {
                getLogger().logConfigMessage(getLogger().IGNORE_ASSOCIATION_OVERRIDE, name, getAttributeName(), getClassAccessor().getJavaClassName(), getJavaClassName());
                associationOverridesMap.put(name, getDescriptor().getAssociationOverrideFor(dotNotationName));
            } else {
                associationOverridesMap.put(name, associationOverride);
            }
        }
        
        // Now add every other descriptor association override that didn't 
        // override a mapping level one (if we are processing a mapping from
        // a mapped superclass level). We'll check the attribute names match
        // and rip off the extra qualifying when adding it to the override map.
        if (getClassAccessor().isMappedSuperclass()) {
            for (AssociationOverrideMetadata associationOverride : getDescriptor().getAssociationOverrides()) {
                String name = associationOverride.getName();
                String attributeName = name;
                String overrideName = name;
                int indexOfFirstDot = name.indexOf(".");                

                if (indexOfFirstDot > -1) {
                    attributeName = name.substring(0, indexOfFirstDot);
                    overrideName = name.substring(indexOfFirstDot + 1);
                }
                 
                if (attributeName.equals(getAttributeName()) && ! associationOverridesMap.containsKey(attributeName)) {
                    associationOverridesMap.put(overrideName, associationOverride);
                }
            }
        }
        
        return associationOverridesMap;
    }
    
    /**
     * INTERNAL:
     * Return the attribute name for this accessor. This is typically the
     * attribute name on the accessible object (i.e., field or property name),
     * however, if access-methods have been specified, use the name attribute
     * that was specified in XML. (e.g. basic name="sin") and not the property
     * name of the get method from the access-methods specification.
     */
    @Override
    public String getAttributeName() {
        if (hasAccessMethods()) {
            return getName();
        } else {
            return getAccessibleObject().getAttributeName();
        }
    }

    /**
     * INTERNAL:
     * Return the attribute override for this accessor.
     */
    protected AttributeOverrideMetadata getAttributeOverride(String loggingCtx) {
        if (loggingCtx.equals(MetadataLogger.MAP_KEY_COLUMN)) {
            return getDescriptor().getAttributeOverrideFor(KEY_DOT_NOTATION + getAttributeName());
        } else if (loggingCtx.equals(MetadataLogger.VALUE_COLUMN)) {
            if (getDescriptor().hasAttributeOverrideFor(VALUE_DOT_NOTATION + getAttributeName())) {
                return getDescriptor().getAttributeOverrideFor(VALUE_DOT_NOTATION + getAttributeName());
            }
        }
        return getDescriptor().getAttributeOverrideFor(getAttributeName());
    }

    /**
     * INTERNAL:
     * Process the list of attribute overrides into a map, merging and 
     * overriding any attribute overrides where necessary with descriptor
     * level attribute overrides.
     */
    protected Map<String, AttributeOverrideMetadata> getAttributeOverrides(List<AttributeOverrideMetadata> attributeOverrides) {
        // TODO: Be nice to look for duplicates within the same list.
        HashMap<String, AttributeOverrideMetadata> attributeOverridesMap = new HashMap<String, AttributeOverrideMetadata>();
        
        for (AttributeOverrideMetadata attributeOverride : attributeOverrides) {
            String name = attributeOverride.getName();
            
            // An attribute override from a sub-entity class will name its
            // attribute override slightly different in that it will have one
            // extra dot notation at the front. E.G. A mapped superclass that
            // defines an embedded attribute named 'record' can define attribute
            // overrides directly on the mapping, that is, 'date'. Whereas
            // from an entity class to override 'date' on 'record', the
            // attribute name will be 'record.date'
            String dotNotationName = getAttributeName() + "." + name;
            if (getClassAccessor().isMappedSuperclass() && getDescriptor().hasAttributeOverrideFor(dotNotationName)) {
                getLogger().logConfigMessage(getLogger().IGNORE_ATTRIBUTE_OVERRIDE, name, getAttributeName(), getClassAccessor().getJavaClassName(), getJavaClassName());
                attributeOverridesMap.put(name, getDescriptor().getAttributeOverrideFor(dotNotationName));
            } else {
                attributeOverridesMap.put(name, attributeOverride);
            }
        }
        
        // Now add every other descriptor association override that didn't 
        // override a mapping level one (if we are processing a mapping from
        // a mapped superclass level). We'll check the attribute names match
        // and rip off the extra qualifying when adding it to the override map.
        if (getClassAccessor().isMappedSuperclass()) {
            for (AttributeOverrideMetadata attributeOverride : getDescriptor().getAttributeOverrides()) {
                String name = attributeOverride.getName();
                String attributeName = name;
                String overrideName = name;
                int indexOfFirstDot = name.indexOf(".");                

                if (indexOfFirstDot > -1) {
                    attributeName = name.substring(0, indexOfFirstDot);
                    overrideName = name.substring(indexOfFirstDot + 1);
                }
                 
                if (attributeName.equals(getAttributeName()) && ! attributeOverridesMap.containsKey(attributeName)) {
                    attributeOverridesMap.put(overrideName, attributeOverride);
                }
            }
        }
        
        return attributeOverridesMap;
    }
    
    /**
     * INTERNAL:
     * Used for OX mapping. Those accessors that do not require a separate 
     * attribute-type specification for VIRTUAL accessors should override this 
     * method. For example, one-to-one and many-to-one will its target-entity. 
     * variable-one-to-one will use its target-interface.
     */
    public String getAttributeType() {
        return m_attributeType;
    }
    
    /**
     * INTERNAL:
     * Returns the class accessor on which this mapping was defined.
     */
    public ClassAccessor getClassAccessor(){
        return m_classAccessor;
    }
    
    /**
     * INTERNAL:
     * Subclasses should override this method to return the appropriate
     * column for their mapping.
     * @see BasicAccessor
     * @see BasicCollectionAccessor
     * @see BasicMapAccessor
     * @see ElementCollectionAccessor
     * @see CollectionAccessor
     */
    protected ColumnMetadata getColumn(String loggingCtx) {
        return m_field == null ? new ColumnMetadata(this) : m_field;
    }

    /**
     * INTERNAL:
     * Given the potential converts return them for processing unless there
     * are overrides available from the descriptor.
     */
    protected List<ConvertMetadata> getConverts(List<ConvertMetadata> potentialConverts) {
        if (getDescriptor().hasConverts(getAttributeName())) {
            return getDescriptor().getConverts(getAttributeName());
        } else {
            return potentialConverts;
        }
    }
    
    /**
     * INTERNAL:
     * Process column metadata details into a database field. This will set 
     * correct metadata and log defaulting messages to the user. It also looks 
     * for an attribute override.
     * 
     * This method will call getColumn() which assumes the subclasses will
     * return the appropriate ColumnMetadata to process based on the context
     * provided.
     * 
     * @See BasicCollectionAccessor and BasicMapAccessor.
     */
    protected DatabaseField getDatabaseField(DatabaseTable defaultTable, String loggingCtx) {
        // Check if we have an attribute override first, otherwise process for a column
        ColumnMetadata column  = hasAttributeOverride(loggingCtx) ? getAttributeOverride(loggingCtx).getColumn() : getColumn(loggingCtx);

        // Get the actual database field and apply any defaults.
        DatabaseField field = column.getDatabaseField();
           
        // Make sure there is a table name on the field.
        if (! field.hasTableName()) {
            field.setTable(defaultTable);
        }
        
        // We must check the flag before blindly setting it on the table since
        // global flag may be false where this fields table may explicitly use
        // delimiters.
        if (getProject().useDelimitedIdentifier()) {
            field.getTable().setUseDelimiters(true);
        }
        
        // Set the correct field name, defaulting and logging when necessary.
        String defaultName = getDefaultAttributeName();
           
        // If this is for a map key column, append a suffix.
        if (loggingCtx.equals(MetadataLogger.MAP_KEY_COLUMN)) {
            defaultName += DEFAULT_MAP_KEY_COLUMN_SUFFIX;
        }
        
        setFieldName(field, defaultName, loggingCtx);

        // Store all the fields for this descriptor. This will allow re-use
        // and easy lookup of referenced column names.
        getDescriptor().addField(field);
        
        return field;
    }
    
    /**
     * INTERNAL:
     */
    protected String getDefaultFetchType() {
        return JPA_FETCH_EAGER; 
    }
    
    /**
     * INTERNAL:
     * Return the default table to hold the foreign key of a MapKey when
     * and Entity is used as the MapKey
     */
    protected DatabaseTable getDefaultTableForEntityMapKey(){
        return getReferenceDescriptor().getPrimaryTable();
    }
    
    /**
     * INTERNAL:
     * Return the enumerated metadata for this accessor.
     * @see DirectAccessor
     * @see ElementCollectionAccessor
     * @see CollectionAccessor
     */
    public EnumeratedMetadata getEnumerated(boolean isForMapKey) {
        return null;
    }
    
    /**
     * INTERNAL:
     * Used for OX mapping.
     */
    public ColumnMetadata getField() {
        return m_field;
    }
    
    /**
     * INTERNAL:
     * Return the foreign key to use with this mapping accessor. This method 
     * will look for association overrides and use the foreign key from it 
     * instead (do as the association override says).
     */
    protected ForeignKeyMetadata getForeignKey(ForeignKeyMetadata potentialForeignKey, MetadataDescriptor descriptor) {
        if (getDescriptor().hasAssociationOverrideFor(getAttributeName())) {
            ForeignKeyMetadata foreignKey = getDescriptor().getAssociationOverrideFor(getAttributeName()).getForeignKey();
            
            // Have to init the foreign key with items that could potentially
            // be required during processing here.
            if (foreignKey != null) {
                foreignKey.setProject(descriptor.getProject());
            }
            
            return foreignKey;
        } else {
            return potentialForeignKey;
        }
    }
    
    /**
     * INTERNAL:
     * Returns the get method name of a method accessor. Note, this method
     * should not be called when processing field access.
     */
    public String getGetMethodName() {
        return hasAccessMethods() ? getAccessMethods().getGetMethodName() : getAccessibleObjectName(); 
    }
    
    /**
     * INTERNAL:
     * Return the join columns to use with this mapping accessor. This method 
     * will look for association overrides and use those instead if some are 
     * available. This method will validate the join columns and default
     * any where necessary.
     */
    protected List<JoinColumnMetadata> getJoinColumns(List<JoinColumnMetadata> potentialJoinColumns, MetadataDescriptor descriptor) {
        if (getDescriptor().hasAssociationOverrideFor(getAttributeName())) {
            return getJoinColumnsAndValidate(getDescriptor().getAssociationOverrideFor(getAttributeName()).getJoinColumns(), descriptor);
        } else {
            return getJoinColumnsAndValidate(potentialJoinColumns, descriptor);
        }
    }
    
    /**
     * INTERNAL:
     * This method will validate the join columns and default any where 
     * necessary.
     */    
    protected List<JoinColumnMetadata> getJoinColumnsAndValidate(List<JoinColumnMetadata> joinColumns, MetadataDescriptor referenceDescriptor) {
        if (joinColumns.isEmpty()) {
            if (referenceDescriptor.hasCompositePrimaryKey()) {
                // Add a default one for each part of the composite primary
                // key. Foreign and primary key to have the same name.
                for (DatabaseField primaryKeyField : referenceDescriptor.getPrimaryKeyFields()) {
                    // Multitenant primary key fields will be dealt with below so avoid adding here.
                    if (! primaryKeyField.isPrimaryKey()) {
                        JoinColumnMetadata joinColumn = new JoinColumnMetadata();
                        joinColumn.setReferencedColumnName(primaryKeyField.getName());
                        joinColumn.setName(primaryKeyField.getName());
                        joinColumn.setProject(referenceDescriptor.getProject());
                        joinColumns.add(joinColumn);
                    }
                }
            } else {
                // Add a default one for the single case, not setting any
                // foreign and primary key names. They will default based
                // on which accessor is using them.
                JoinColumnMetadata jcm = new JoinColumnMetadata();
                jcm.setProject(referenceDescriptor.getProject());
                joinColumns.add(jcm);
            }
        } else {
            // Need to update any join columns that use a foreign key name
            // for the primary key name. E.G. User specifies the renamed id
            // field name from a primary key join column as the primary key in
            // an inheritance subclass.
            for (JoinColumnMetadata joinColumn : joinColumns) {
                // Doing this could potentially change a value entered in XML.
                // However, in this case I think that is ok since in theory we 
                // are writing out the correct value that EclipseLink needs to 
                // form valid queries.
                String referencedColumnName = joinColumn.getReferencedColumnName();
                // The referenced column name in a variable one to one case is a 
                // query key name and not a column name so bypass any of this
                // code.
                if (referencedColumnName != null && !isVariableOneToOne()) {
                    DatabaseField referencedField = new DatabaseField();
                    setFieldName(referencedField, referencedColumnName);
                    joinColumn.setReferencedColumnName(referenceDescriptor.getPrimaryKeyJoinColumnAssociation(referencedField).getName());
                }
            }
        }
        
        // Multitenant entities. Go through and add any multitenant primary key 
        // fields that need to be added. The user may or may not specify them
        // in metadata. 
        if (referenceDescriptor.hasSingleTableMultitenant() && joinColumns.size() != referenceDescriptor.getPrimaryKeyFields().size()) {
            Map<String, List<DatabaseField>> referenceTenantFields = referenceDescriptor.getSingleTableMultitenantFields();
                
            // If we are multitenant then we can sync up on relationship fields
            // using the context property.
            Map<String, List<DatabaseField>> tenantFields = getDescriptor().hasSingleTableMultitenant() ? getDescriptor().getSingleTableMultitenantFields() : null;
                    
            for (String contextProperty : referenceTenantFields.keySet()) {
                List<DatabaseField> referenceFields = referenceTenantFields.get(contextProperty);

                for (DatabaseField referenceField : referenceFields) {
                    // Only if it is a primary key field, otherwise we don't
                    // care about it.
                    if (referenceField.isPrimaryKey()) {
                        JoinColumnMetadata jcm = new JoinColumnMetadata();
                        // This join column must be read only.
                        jcm.setInsertable(false);
                        jcm.setUpdatable(false);
                        
                        // If we have a related context property, look up a 
                        // field that matches from it.
                        if (tenantFields != null && tenantFields.containsKey(contextProperty)) {
                            // This is going to return a list just pick the first
                            // field (they populate the same value so doesn't really
                            // matter which one we pick.
                            jcm.setName(tenantFields.get(contextProperty).get(0).getName());
                        } else {
                            // We don't have a match, use the same name.
                            jcm.setName(referenceField.getName());
                        }
                            
                        jcm.setReferencedColumnName(referenceField.getName());
                        jcm.setProject(referenceDescriptor.getProject());
                        joinColumns.add(jcm);
                    }
                }
            } 
        }
        
        // Now run some validation.
        if (referenceDescriptor.hasCompositePrimaryKey()) {
            // The number of join columns should equal the number of primary key fields.
            if (joinColumns.size() != referenceDescriptor.getPrimaryKeyFields().size()) {
                throw ValidationException.incompleteJoinColumnsSpecified(getAnnotatedElement(), getJavaClass());
            }
            
            // All the primary and foreign key field names should be specified.
            for (JoinColumnMetadata joinColumn : joinColumns) {
                if (joinColumn.isPrimaryKeyFieldNotSpecified() || joinColumn.isForeignKeyFieldNotSpecified()) {
                    throw ValidationException.incompleteJoinColumnsSpecified(getAnnotatedElement(), getJavaClass());
                }
            }
        }
        
        return joinColumns;
    }
    
    /**
     * INTERNAL:
     * Return the lob metadata for this accessor.
     * @see DirectAccessor
     */
    public LobMetadata getLob(boolean isForMapKey) {
        return null;
    }
    
    /**
     * INTERNAL:
     * Return the mapping that this accessor is associated to.
     */
    public DatabaseMapping getMapping(){
        return (m_overrideMapping == null) ? m_mapping : m_overrideMapping;
    }
    
    /**
     * INTERNAL:
     * Return the owning descriptor of this accessor.
     */
    public MetadataDescriptor getOwningDescriptor() {
        return getClassAccessor().getOwningDescriptor();
    }
    
    /**
     * INTERNAL:
     * Return the owning descriptors of this accessor. In most cases this is
     * a single descriptor. Multiples can only exist when dealing with 
     * accessors for an embeddable that is shared.
     */
    public List<MetadataDescriptor> getOwningDescriptors() {
        return getClassAccessor().getOwningDescriptors();
    }
    
    /**
     * INTERNAL:
     * Return the map key if this mapping accessor employs one. Those accessors
     * that support it should override this method.
     * @see CollectionAccessor
     * @see ElementCollectionAccessor
     */
    public MapKeyMetadata getMapKey() {
        return null;
    }
    
    /**
     * INTERNAL:
     * Given the potential converts return them for processing unless there
     * are overrides available from the descriptor.
     */
    protected List<ConvertMetadata> getMapKeyConverts(List<ConvertMetadata> potentialMapKeyConverts) {
        if (getDescriptor().hasMapKeyConverts(getAttributeName())) {
            return getDescriptor().getMapKeyConverts(getAttributeName());
        } else {
            return potentialMapKeyConverts;
        }
    }
    
    /**
     * INTERNAL:
     * Return the map key reference class for this accessor if applicable. It 
     * will try to extract a reference class from a generic specification.
     * Parameterized generic keys on a MappedSuperclass will return void.class.  
     * If no generics are used, then it will return void.class. This avoids NPE's 
     * when processing JPA converters that can default (Enumerated and Temporal) 
     * based on the reference class.
     */
    public MetadataClass getMapKeyReferenceClass() {
        // First check if we are a mapped key map accessor and return its map
        // key class if specified. Otherwise continue on to extract it from
        // a generic specification. We do this to avoid going to the class
        // with is needed for dynamic persistence.
        if (isMappedKeyMapAccessor()) {
            MetadataClass mapKeyClass = ((MappedKeyMapAccessor) this).getMapKeyClass();
            if (mapKeyClass != null && ! mapKeyClass.isVoid()) {
                return mapKeyClass;
            }
        }
        
        if (isMapAccessor()) {
            MetadataClass referenceClass = getAccessibleObject().getMapKeyClass(getDescriptor());
        
            if (referenceClass == null) {
                throw ValidationException.unableToDetermineMapKeyClass(getAttributeName(), getJavaClass());
            }        
        
            // 266912:  Use of parameterized generic types like Map<X,Y> 
            // inherits from class<T> in a MappedSuperclass field will cause 
            // referencing issues - as in we are unable to determine the correct 
            // type for T. A workaround for this is to detect when we are in 
            // this state and return a standard top level class. An invalid 
            // class will be of the form MetadataClass.m_name="T" 
            if (getDescriptor().isMappedSuperclass()) {
                // Determine whether we are directly referencing a class or 
                // using a parameterized generic reference by trying to load the 
                // class and catching any validationException. If we do not get 
                // an exception on getClass then the referenceClass.m_name is 
                // valid and should be directly returned
                try {
                    MetadataHelper.getClassForName(referenceClass.getName(), getMetadataFactory().getLoader());
                } catch (ValidationException exception) {
                    // Default to Void for parameterized types
                    // Ideally we would need a MetadataClass.isParameterized() to inform us instead.
                    return getMetadataClass(Void.class);
                }                          
            }
            
            return referenceClass;
        } else {
            return getMetadataClass(void.class);
        }
    }
    
    /**
     * INTERNAL:
     * Return the map key reference class name
     */
    public String getMapKeyReferenceClassName() {
        return getMapKeyReferenceClass().getName();
    }
    
    /**
     * INTERNAL:
     * Return the map key reference class for this accessor if applicable. It 
     * will try to extract a reference class from a generic specification.  
     * If no generics are used, then it will return void.class. This avoids NPE's 
     * when processing JPA converters that can default (Enumerated and Temporal) 
     * based on the reference class.
     * 
     * Future: this method is where we would provide a more explicit reference
     * class to support an auto-apply jpa converter. Per the spec auto-apply
     * converters are applied against basics only.
     */
    public MetadataClass getMapKeyReferenceClassWithGenerics() {
        return getMapKeyReferenceClass();
    }
    
    /**
     * INTERNAL:
     * Return the raw class for this accessor. 
     * E.g. For an accessor with a type of java.util.Collection<Employee>, this 
     * method will return java.util.Collection. To check for the attribute
     * type we must go through the method calls since some accessors define
     * the attribute type through a target entity specification. Do not access
     * the m_attributeType variable directly in this method.
     */
    public MetadataClass getRawClass() {
        if (hasAttributeType()) {
            // If the class doesn't exist the factory we'll just return a
            // generic MetadataClass
            return getMetadataClass(getAttributeType());
        } else {
            return getAccessibleObject().getRawClass(getDescriptor());
        }
    }
    
    /**
     * INTERNAL:
     * Return the raw class with any generic specifications for this accessor. 
     * E.g. For an accessor with a type of java.util.Collection<Employee>, this 
     * method will return java.util.CollectionEmployee. To check for the 
     * attribute type we must go through the method calls since some accessors 
     * define the attribute type through a target entity specification. Do not 
     * access the m_attributeType variable directly in this method.
     */
    public MetadataClass getRawClassWithGenerics() {
        if (hasAttributeType()) {
            // If the class doesn't exist the factory we'll just return a
            // generic MetadataClass
            return getMetadataClass(getAttributeType());
        } else {
            return getAccessibleObject().getRawClassWithGenerics(getDescriptor());
        }
    }
    
    /**
     * INTERNAL:
     * Return the mapping accessors associated with the reference descriptor.
     */
    public Collection<MappingAccessor> getReferenceAccessors() {
        return getReferenceDescriptor().getMappingAccessors();
    }
    
    /**
     * INTERNAL: 
     * Return the reference class for this accessor. By default the reference
     * class is the raw class. Some accessors may need to override this
     * method to drill down further. That is, try to extract a reference class
     * from generics.
     */
    public MetadataClass getReferenceClass() {
        return getRawClass();
    }
    
    /**
     * INTERNAL: 
     * Return the reference class for this accessor. By default the reference
     * class is the raw class. Some accessors may need to override this
     * method to drill down further. That is, try to extract a reference class
     * from generics.
     */
    public MetadataClass getReferenceClassWithGenerics() {
        return getRawClassWithGenerics();
    }
    
    /**
     * INTERNAL:
     * Attempts to return a reference class from a generic specification. Note,
     * this method may return null.
     */
    public MetadataClass getReferenceClassFromGeneric() {
        return getAccessibleObject().getReferenceClassFromGeneric(getDescriptor());
    }

    /**
     * INTERNAL:
     * Return the reference class name for this accessor.
     */
    public String getReferenceClassName() {
        return getReferenceClass().getName();
    }
    
    /**
     * INTERNAL:
     * Return the reference descriptors table. By default it is the primary
     * key table off the reference descriptor. Subclasses that care to return
     * a different class should override this method.
     * @see DirectCollectionAccessor
     * @see ManyToManyAccessor
     */
    protected DatabaseTable getReferenceDatabaseTable() {
        return getReferenceDescriptor().getPrimaryKeyTable();
    }
    
    /**
     * INTERNAL:
     * Return the reference metadata descriptor for this accessor.
     */
    public MetadataDescriptor getReferenceDescriptor() {
        ClassAccessor accessor = getProject().getAccessor(getReferenceClassName());
        
        if (accessor == null) {
            throw ValidationException.classNotListedInPersistenceUnit(getReferenceClassName());
        }
        
        return accessor.getDescriptor();
    }

    /**
     * INTERNAL:
     * Returns the set method name of a method accessor. Note, this method
     * should not be called when processing field access.
     */
    public String getSetMethodName() {
        return hasAccessMethods() ? getAccessMethods().getSetMethodName() : ((MetadataMethod) getAccessibleObject()).getSetMethodName();
    }
    
    /**
     * INTERNAL:
     * Return the temporal metadata for this accessor.
     * @see DirectAccessor
     * @see CollectionAccessor
     */
    public TemporalMetadata getTemporal(boolean isForMapKey) {
        return null;
    }
    
    /**
     * INTERNAL: Set the temporal metadata for this accessor.
     * 
     * @see DirectAccessor
     * @see CollectionAccessor
     */
    protected void setTemporal(TemporalMetadata metadata, boolean isForMapKey) {

    }
    
    /**
     * INTERNAL:
     * Return true if we have an attribute override for this accessor.
     */
    protected boolean hasAttributeOverride(String loggingCtx) {
        if (loggingCtx.equals(MetadataLogger.MAP_KEY_COLUMN)) {
            return getDescriptor().hasAttributeOverrideFor(KEY_DOT_NOTATION + getAttributeName());
        } else if (loggingCtx.equals(MetadataLogger.VALUE_COLUMN)) {
            if (getDescriptor().hasAttributeOverrideFor(VALUE_DOT_NOTATION + getAttributeName())) {
                return true;
            }
        } 
            
        return getDescriptor().hasAttributeOverrideFor(getAttributeName());
    }
    
    /**
     * INTERNAL:
     * Those accessors that do not require a separate attribute-type 
     * specification for VIRTUAL accessors should override this method. For
     * example, one-to-one and many-to-one will its target-entity. 
     * variable-one-to-one will use its target-interface. 
     */
    public boolean hasAttributeType() {
        return m_attributeType != null;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor has temporal metadata.
     * @see DirectAccessor
     * @see ElementCollectionAccessor
     * @see CollectionAccessor
     */
    protected boolean hasEnumerated(boolean isForMapKey) {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor has lob metadata.
     * @see DirectAccessor
     * @see ElementCollectionAccessor
     * @see CollectionAccessor
     */
    protected boolean hasLob(boolean isForMapKey) {
        return false;
    }
    
    /**
     * INTERNAL:
     * Method should be overridden by those accessors that accept and use a map 
     * key.
     * @see CollectionAccessor
     * @see ElementCollectionAccessor
     * @see BasicMapAccessor
     */
    public boolean hasMapKey() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Method to check if this accessor has a ReturnInsert annotation.
     */
    protected boolean hasReturnInsert() {
        return isAnnotationPresent(ReturnInsert.class);
    }
    
    /**
     * INTERNAL:
     * Method to check if this accessor has a ReturnUpdate annotation.
     */
    protected boolean hasReturnUpdate() {
        return isAnnotationPresent(ReturnUpdate.class);
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor has temporal metadata.
     * @see DirectAccessor
     * @see ElementCollectionAccessor
     * @see CollectionAccessor
     */
    public boolean hasTemporal(boolean isForMapKey) {
        return false;
    }
    
    /**
     * INTERNAL: 
     * Init an xml mapping accessor with its necessary components. 
     */
    public void initXMLMappingAccessor(ClassAccessor classAccessor) {
        m_classAccessor = classAccessor;
        setEntityMappings(classAccessor.getEntityMappings());
        initXMLAccessor(classAccessor.getDescriptor(), classAccessor.getProject());   
    }
    
    /**
     * INTERNAL:
     */
    @Override
    public void initXMLObject(MetadataAccessibleObject accessibleObject, XMLEntityMappings entityMappings) {
        super.initXMLObject(accessibleObject, entityMappings);

        initXMLObject(m_field, accessibleObject);
    }
    
    /** 
     * INTERNAL:
     * Indicates whether the specified annotation is present on the annotated
     * element for this accessor. Method checks against the metadata complete
     * flag.
     */
    @Override
    public boolean isAnnotationPresent(String annotation) {
        return getAccessibleObject().isAnnotationPresent(annotation, getClassAccessor());
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a basic mapping.
     */
    public boolean isBasic() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a basic collection mapping.
     */
    public boolean isBasicCollection() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a basic map mapping.
     */
    public boolean isBasicMap() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor is a derived id class accessor.
     */
    public boolean isDerivedIdClass(){
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a direct collection mapping, 
     * which include basic collection, basic map and element collection 
     * accessors.
     */
    public boolean isDirectCollection() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents an element collection that
     * contains embeddable objects.
     */
    public boolean isDirectEmbeddableCollection() {
        return false;
    }
    
    /** 
     * INTERNAL:
     * Return true if this accessor represents a collection accessor.
     */
    public boolean isCollectionAccessor() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents an aggregate mapping.
     */
    public boolean isEmbedded() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents an aggregate id mapping.
     */
    public boolean isEmbeddedId() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this represents an enum type mapping. Will return true
     * if the accessor's reference class is an enum or if enumerated metadata
     * exists.
     */
    protected boolean isEnumerated(MetadataClass referenceClass, boolean isForMapKey) {
        return hasEnumerated(isForMapKey) || EnumeratedMetadata.isValidEnumeratedType(referenceClass);
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor is part of the id.
     */
    public boolean isId(){
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a BLOB/CLOB mapping.
     */
    protected boolean isLob(MetadataClass referenceClass, boolean isForMapKey) {
        return hasLob(isForMapKey);
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a m-m relationship.
     */
    public boolean isManyToMany() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a m-1 relationship.
     */
    public boolean isManyToOne() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor uses a Map.
     */
    public boolean isMapAccessor() {
        return getAccessibleObject().isSupportedMapClass(getRawClass());
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor is a mapped key map accessor. It is a
     * map key accessor for two reasons, it's a map and it does not have 
     * a map key specified. NOTE: we can't check for a map key class since
     * one may not have been explicitly specified. In this case, a generic 
     * value must be set and we check for one when adding accessors (and in 
     * turn set the map key class at that point)
     */
    public boolean isMappedKeyMapAccessor() {
        return MappedKeyMapAccessor.class.isAssignableFrom(getClass()) && isMapAccessor() && ! hasMapKey();
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor is a multitenant id mapping.
     */
    public boolean isMultitenantId() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a 1-m relationship.
     */
    public boolean isOneToMany() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a 1-1 relationship.
     */
    public boolean isOneToOne() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Returns true is the given class is primitive wrapper type.
     */
    protected boolean isPrimitiveWrapperClass(MetadataClass cls) {
        return cls.extendsClass(Number.class) ||
            cls.equals(Boolean.class) ||
            cls.equals(Character.class) ||
            cls.equals(String.class) ||
            cls.extendsClass(java.math.BigInteger.class) ||
            cls.extendsClass(java.math.BigDecimal.class) ||
            cls.extendsClass(java.util.Date.class) ||
            cls.extendsClass(java.util.Calendar.class);
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor has been processed. If there is a mapping
     * set, we have processed this accessor.
     */
    @Override
    public boolean isProcessed() {
        return m_mapping != null;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor method represents a relationship.
     */
    public boolean isRelationship() {
        return isManyToOne() || isManyToMany() || isOneToMany() || isOneToOne() || isVariableOneToOne();
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a serialized mapping.
     */
    public boolean isSerialized(MetadataClass referenceClass, boolean isForMapKey) {
        return isValidSerializedType(referenceClass);
    }
    
    /**
     * INTERNAL:
     * Return true if this represents a temporal type mapping.
     */
    protected boolean isTemporal(MetadataClass referenceClass, boolean isForMapKey) {
        return hasTemporal(isForMapKey) || TemporalMetadata.isValidTemporalType(referenceClass);
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a transient mapping.
     */
    public boolean isTransient() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Returns true if the given class is valid for SerializedObjectMapping.
     */
    protected boolean isValidSerializedType(MetadataClass cls) {
        if (cls.isPrimitive()) {
            return false;
        }
        
        if (isPrimitiveWrapperClass(cls)) {    
            return false;
        }   
        
        if (LobMetadata.isValidLobType(cls)) {
            return false;
        }
        
        if (TemporalMetadata.isValidTemporalType(cls)) {
            return false;
        }
     
        return true;   
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a variable one to one mapping.
     */
    public boolean isVariableOneToOne() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Process an association override for either an embedded object mapping, 
     * or a map mapping (element-collection, 1-M and M-M) containing an
     * embeddable object as the value or key.
     * This method should be implemented in those accessors that support 
     * association overrides. An exception is thrown otherwise the association
     * is called against an unsupported accessor/relationship. 
     */
    protected void processAssociationOverride(AssociationOverrideMetadata associationOverride, EmbeddableMapping embeddableMapping, MetadataDescriptor owningDescriptor) {
        throw ValidationException.invalidEmbeddableAttributeForAssociationOverride(getJavaClass(), getAttributeName(), associationOverride.getName(), associationOverride.getLocation()); 
    }
    
    /**
     * INTERNAL:
     * Process the association overrides for the given embeddable mapping which
     * is either an embedded or element collection mapping. Association 
     * overrides are used to specify different keys to a shared mapping.
     */
    protected void processAssociationOverrides(List<AssociationOverrideMetadata> associationOverrides, EmbeddableMapping embeddableMapping, MetadataDescriptor embeddableDescriptor) {
        // Get the processible map of association overrides. This will take dot 
        // notation overrides into consideration (from a sub-entity to a mapped 
        // superclass accessor) and merge the lists. Once the map is returned, 
        // use the map keys as the attribute name and not the name from the 
        // individual association override since they could still contain dot 
        // notation names meaning you will not find their respective mapping 
        // accessor on the embeddable descriptor.
        Map<String, AssociationOverrideMetadata> mergedAssociationOverrides = getAssociationOverrides(associationOverrides);
        
        for (String attributeName : mergedAssociationOverrides.keySet()) {
            AssociationOverrideMetadata associationOverride = mergedAssociationOverrides.get(attributeName);
            // The getAccessorFor call will take care of any sub dot notation 
            // attribute names when looking for the mapping. It will traverse 
            // the embeddable chain. 
            MappingAccessor mappingAccessor = embeddableDescriptor.getMappingAccessor(attributeName);
            
            if (mappingAccessor == null) {
                throw ValidationException.embeddableAssociationOverrideNotFound(embeddableDescriptor.getJavaClass(), attributeName, getJavaClass(), getAttributeName());
            } else {
                mappingAccessor.processAssociationOverride(associationOverride, embeddableMapping, getOwningDescriptor());
            }  
        }
    }
    
    /**
     * INTERNAL:
     * Process the attribute overrides for the given embedded mapping. Attribute 
     * overrides are used to apply the correct field name translations of direct 
     * fields. Note an embedded object mapping may be supported as the map key
     * to an element-collection, 1-M and M-M mapping.
     */
    protected void processAttributeOverrides(List<AttributeOverrideMetadata> attributeOverrides, AggregateObjectMapping aggregateObjectMapping, MetadataDescriptor embeddableDescriptor) {
        // Get the processible map of attribute overrides. This will take dot 
        // notation overrides into consideration (from a sub-entity to a mapped 
        // superclass accessor) and merge the lists. Once the map is returned, 
        // use the map keys as the attribute name and not the name from the 
        // individual attribute override since they could still contain dot 
        // notation names meaning you will not find their respective mapping 
        // accessor on the embeddable descriptor.
        Map<String, AttributeOverrideMetadata> mergedAttributeOverrides = getAttributeOverrides(attributeOverrides);
        
        for (String attributeName : mergedAttributeOverrides.keySet()) {
            AttributeOverrideMetadata attributeOverride = mergedAttributeOverrides.get(attributeName);
            // The getMappingForAttributeName call will take care of any sub dot 
            // notation attribute names when looking for the mapping. It will
            // traverse the embeddable chain. 
            MappingAccessor mappingAccessor = embeddableDescriptor.getMappingAccessor(attributeName);

            String colName = attributeOverride.getColumn().getName();
            if (colName == null || colName.isEmpty()) {
                String prevName = mappingAccessor.getDefaultAttributeName();
                attributeOverride.getColumn().setName(prevName);
            }
            
            if (mappingAccessor == null) {
                throw ValidationException.embeddableAttributeOverrideNotFound(embeddableDescriptor.getJavaClass(), attributeName, getJavaClass(), getAttributeName());
            } else if (! mappingAccessor.isBasic()) {
                throw ValidationException.invalidEmbeddableAttributeForAttributeOverride(embeddableDescriptor.getJavaClass(), attributeName, getJavaClass(), getAttributeName());
            } else {
                // Get databasefield() takes care of any delimited/uppercasing on the column.
                addFieldNameTranslation(aggregateObjectMapping, attributeName, attributeOverride.getColumn().getDatabaseField(), mappingAccessor);
            }
        }
    }
    
    /**
     * INTERNAL:
     * Process the map metadata if this is a valid map accessor. Will return 
     * the map key method name that should be use, null otherwise.
     * @see CollectionAccessor
     * @see ElementCollectionAccessor
     */
    protected void processContainerPolicyAndIndirection(ContainerMapping mapping) {
        if (isMappedKeyMapAccessor()) {
            // If we are a map key map accessor then the following is true:
            // 1 - we implement the mapped key map accessor interface
            // 2 - we are a map accessor
            // 3 - there is no map key metadata specified
            processMapKeyClass(mapping, (MappedKeyMapAccessor) this);
        } else if (isMapAccessor()) {
            // If we are not a mapped key map accessor, but a map accessor,
            // we need a map key metadata object to process. Default one if
            // one is not provided.
            MapKeyMetadata mapKey = getMapKey();
            if (mapKey == null) {
                setIndirectionPolicy(mapping, new MapKeyMetadata().process(mapping, this), usesIndirection());
            } else {
                setIndirectionPolicy(mapping, mapKey.process(mapping, this), usesIndirection());
            }
        } else {
            // Set the indirection policy on the mapping.
            setIndirectionPolicy(mapping, null, usesIndirection());
        }
    } 
    
    /**
     * INTERNAL:
     * Process a Convert annotation or convert element to apply to specified 
     * EclipseLink converter (Converter, TypeConverter, ObjectTypeConverter) 
     * to the given mapping.
     */
    protected void processConvert(DatabaseMapping mapping, String converterName, MetadataClass referenceClass, boolean isForMapKey, boolean hasConverts) {
        if (converterName.equals(Convert.SERIALIZED)) {
            processSerialized(mapping, referenceClass, isForMapKey);
        } else if (converterName.equals(Convert.CLASS_INSTANCE)){
            new ClassInstanceMetadata().process(mapping, this, referenceClass, isForMapKey);
        } else if (converterName.equals(Convert.XML)){
            new XMLMetadata().process(mapping, this, referenceClass, isForMapKey);
        } else if (converterName.equals(Convert.JSON)){
            new JSONMetadata().process(mapping, this, referenceClass, isForMapKey);
        } else if (converterName.equals(Convert.KRYO)){
            new KryoMetadata().process(mapping, this, referenceClass, isForMapKey);
        } else {
            AbstractConverterMetadata converter = getProject().getConverter(converterName);
                
            if (converter == null) {
                throw ValidationException.converterNotFound(getJavaClass(), converterName, getAnnotatedElement());
            } else {
                // Process the converter for this mapping.
                converter.process(mapping, this, referenceClass, isForMapKey);
            }
        }
        
        // This was an old requirement from PM, that if an EclipseLink
        // convert was specified with a JPA converter that we log a warning
        // message that we're overriding the JPA converter.
        
        if (hasEnumerated(isForMapKey)) {
            getLogger().logWarningMessage(MetadataLogger.IGNORE_ENUMERATED, getJavaClass(), getAnnotatedElement());
        }
        
        if (hasLob(isForMapKey)) {
            getLogger().logWarningMessage(MetadataLogger.IGNORE_LOB, getJavaClass(), getAnnotatedElement());
        }
        
        if (hasTemporal(isForMapKey)) {
            getLogger().logWarningMessage(MetadataLogger.IGNORE_TEMPORAL, getJavaClass(), getAnnotatedElement());
        }
        
        if (isValidSerializedType(referenceClass)) {
            getLogger().logConfigMessage(MetadataLogger.IGNORE_SERIALIZED, getJavaClass(), getAnnotatedElement());
        }
        
        // Check for the new JPA 2.1 convert metadata.
        if (hasConverts) {
            getLogger().logWarningMessage(MetadataLogger.IGNORE_CONVERTS, getJavaClass(), getAnnotatedElement());
        }
        
        if (getProject().hasAutoApplyConverter(referenceClass)) {
            getLogger().logWarningMessage(MetadataLogger.IGNORE_AUTO_APPLY_CONVERTER, getJavaClass(), getAnnotatedElement());
        }
    }
    
    /**
     * INTERNAL:
     * Process the JPA defined convert(s)
     */
    protected void processConverts(List<ConvertMetadata> converts, DatabaseMapping mapping, MetadataClass referenceClass, boolean isForMapKey) {
        if (converts != null) {
            for (ConvertMetadata convert : converts) {
                convert.process(mapping, referenceClass, getClassAccessor(), isForMapKey);
            }
        }
    }
    
    /**
     * INTERNAL:
     */
    protected AbstractDirectMapping processDirectMapKeyClass(MappedKeyMapAccessor mappedKeyMapAccessor) {
        AbstractDirectMapping keyMapping = new DirectToFieldMapping();

        // Get the map key field, defaulting and looking for attribute 
        // overrides. Set the field before applying a converter.
        DatabaseField mapKeyField = getDatabaseField(getReferenceDatabaseTable(), MetadataLogger.MAP_KEY_COLUMN);
        keyMapping.setField(mapKeyField);
        keyMapping.setIsReadOnly(mapKeyField.isReadOnly());
        keyMapping.setAttributeClassificationName(mappedKeyMapAccessor.getMapKeyClass().getName());
        keyMapping.setDescriptor(getDescriptor().getClassDescriptor());
        
        // Process a convert key or jpa converter for the map key if specified.
        processMappingKeyConverter(keyMapping, mappedKeyMapAccessor.getMapKeyConvert(), mappedKeyMapAccessor.getMapKeyConverts(), mappedKeyMapAccessor.getMapKeyClass(), mappedKeyMapAccessor.getMapKeyClassWithGenerics());
        
        return keyMapping;
    }
    
    /**
     * INTERNAL:
     */
    protected AggregateObjectMapping processEmbeddableMapKeyClass(MappedKeyMapAccessor mappedKeyMapAccessor) {
        AggregateObjectMapping keyMapping = new AggregateObjectMapping();
        MetadataClass mapKeyClass = mappedKeyMapAccessor.getMapKeyClass();
        keyMapping.setReferenceClassName(mapKeyClass.getName());
        
        // The embeddable accessor must be processed by now. If it is not then
        // we are in trouble since by the time we get here, we are too late in
        // the cycle to process embeddable classes and their accessors. See
        // MetadataProject processStage3(), processEmbeddableMappingAccessors. 
        // At this stage all class accessors (embeddable, entity and mapped 
        // superclass) have to have been processed to gather all their 
        // relational and embedded mappings. 
        EmbeddableAccessor mapKeyAccessor = getProject().getEmbeddableAccessor(mapKeyClass);
        
        // Ensure the reference descriptor is marked as an embeddable collection.
        mapKeyAccessor.getDescriptor().setIsEmbeddable();
        
        // Process the attribute overrides for this may key embeddable.
        processAttributeOverrides(mappedKeyMapAccessor.getMapKeyAttributeOverrides(), keyMapping, mapKeyAccessor.getDescriptor());
        
        // Process the association overrides for this may key embeddable.
        processAssociationOverrides(mappedKeyMapAccessor.getMapKeyAssociationOverrides(), keyMapping, mapKeyAccessor.getDescriptor());
        
        // Process a convert key or jpa converter for the map key if specified.
        processConverts(getMapKeyConverts(mappedKeyMapAccessor.getMapKeyConverts()), keyMapping, mappedKeyMapAccessor.getMapKeyClass(), true);
        
        keyMapping.setDescriptor(getDescriptor().getClassDescriptor());
        
        return keyMapping;
    }
    
    /**
     * INTERNAL:
     * Process the map key to be an entity class.
     */
    protected OneToOneMapping processEntityMapKeyClass(MappedKeyMapAccessor mappedKeyMapAccessor) {
        String mapKeyClassName = mappedKeyMapAccessor.getMapKeyClass().getName();
        
        // Create the one to one map key mapping.
        OneToOneMapping keyMapping = new OneToOneMapping();
        keyMapping.setReferenceClassName(mapKeyClassName);
        keyMapping.dontUseIndirection();
        keyMapping.setDescriptor(getDescriptor().getClassDescriptor());
        
        // Process the map key join columns.
        EntityAccessor mapKeyAccessor = getProject().getEntityAccessor(mapKeyClassName);
        MetadataDescriptor mapKeyClassDescriptor = mapKeyAccessor.getDescriptor();
        
        // If the fk field (name) is not specified, it defaults to the 
        // concatenation of the following: the name of the referencing 
        // relationship property or field of the referencing entity or 
        // embeddable; "_"; "KEY"
        String defaultFKFieldName = getAttributeName() + DEFAULT_MAP_KEY_COLUMN_SUFFIX;

        // Get the join columns (directly or through an association override), 
        // init them and validate.
        List<JoinColumnMetadata> joinColumns = getJoinColumns(mappedKeyMapAccessor.getMapKeyJoinColumns(), mapKeyClassDescriptor);
        
        // Get the foreign key (directly or through an association override) and
        // make sure it is initialized for processing.
        ForeignKeyMetadata foreignKey = getForeignKey(mappedKeyMapAccessor.getMapKeyForeignKey(), mapKeyClassDescriptor);
        
        // Now process the foreign key relationship metadata.
        processForeignKeyRelationship(keyMapping, joinColumns, foreignKey, mapKeyClassDescriptor, defaultFKFieldName, getDefaultTableForEntityMapKey());

        return keyMapping;
    }
    
    /**
     * INTERNAL:
     * Process an Enumerated setting. The method may still be called if no 
     * Enumerated metadata has been specified but the accessor's reference 
     * class is a valid enumerated type.
     */
    protected void processEnumerated(EnumeratedMetadata enumerated, DatabaseMapping mapping, MetadataClass referenceClass, boolean isForMapKey) {
        if (enumerated == null) {
            enumerated = new EnumeratedMetadata(this);
        }
        
        enumerated.process(mapping, this, referenceClass, isForMapKey);
    }
    
    /**
     * INTERNAL:
     * Process the indirection (aka fetch type)
     */
    protected void processIndirection(ForeignReferenceMapping mapping) {
        boolean usesIndirection = usesIndirection();
        
        // Lazy is not disabled until descriptor initialization (OneToOneMapping preInitialize),
        // as it cannot be known if weaving occurred until then.
        String actualAttributeType = getAttributeType();
        if (getAccessibleObject() != null){
            actualAttributeType = getAccessibleObject().getType();
        }
        
        if (usesIndirection && usesPropertyAccess()) {
            mapping.setIndirectionPolicy(new WeavedObjectBasicIndirectionPolicy(getGetMethodName(), getSetMethodName(), actualAttributeType, true));
        } else if (usesIndirection && usesFieldAccess()) {
            mapping.setIndirectionPolicy(new WeavedObjectBasicIndirectionPolicy(Helper.getWeavedGetMethodName(mapping.getAttributeName()), Helper.getWeavedSetMethodName(mapping.getAttributeName()), actualAttributeType, false));
        } else {
            mapping.setUsesIndirection(usesIndirection);
        }
    }
    
    /**
     * INTERNAL:
     * Return the mapping join fetch type.
     */
    protected void processJoinFetch(String joinFetch, ForeignReferenceMapping mapping) {
        if (joinFetch == null) {
            mapping.setJoinFetch(ForeignReferenceMapping.NONE);
        } else if (joinFetch.equals(JoinFetchType.INNER.name())) {
            mapping.setJoinFetch(ForeignReferenceMapping.INNER_JOIN);
        } else {
            mapping.setJoinFetch(ForeignReferenceMapping.OUTER_JOIN);
        }
    }
    
    /**
     * INTERNAL:
     * Process a lob specification. The lob must be specified to process and 
     * create a lob type mapping.
     */
    protected void processLob(LobMetadata lob, DatabaseMapping mapping, MetadataClass referenceClass, boolean isForMapKey) {
        lob.process(mapping, this, referenceClass, isForMapKey);
    }
    
    /**
     * INTERNAL:
     * Process a map key class for the given map key map accessor.
     */
    protected void processMapKeyClass(ContainerMapping mapping, MappedKeyMapAccessor mappedKeyMapAccessor) {
        MapKeyMapping keyMapping;
        MetadataClass mapKeyClass = mappedKeyMapAccessor.getMapKeyClass();
        
        if (getProject().hasEntity(mapKeyClass)) {
            keyMapping = processEntityMapKeyClass(mappedKeyMapAccessor);
        } else if (getProject().hasEmbeddable(mapKeyClass)) {
            keyMapping = processEmbeddableMapKeyClass(mappedKeyMapAccessor);
        } else {
            keyMapping = processDirectMapKeyClass(mappedKeyMapAccessor);
        }
          
        Class containerClass;
        if (mapping instanceof ForeignReferenceMapping) {
            if (usesIndirection()) {
                containerClass = ClassConstants.IndirectMap_Class;
                ((ForeignReferenceMapping) mapping).setIndirectionPolicy(new TransparentIndirectionPolicy());
            } else {
                containerClass = java.util.Hashtable.class;
                ((ForeignReferenceMapping) mapping).dontUseIndirection();
            }
        } else {
            containerClass = java.util.Hashtable.class;
        }

        MappedKeyMapContainerPolicy policy = new MappedKeyMapContainerPolicy(containerClass);
        policy.setKeyMapping(keyMapping);
        policy.setValueMapping((MapComponentMapping) mapping);
        mapping.setContainerPolicy(policy);
    }
    
    /**
     * INTERNAL:
     * Process a convert value which specifies the name of an EclipseLink
     * converter to process with this accessor's mapping.     
     */
    protected void processMappingConverter(DatabaseMapping mapping, String convertValue, List<ConvertMetadata> converts, MetadataClass referenceClass, MetadataClass referenceClassWithGenerics, boolean isForMapKey) {
        boolean hasConverts = (converts != null && ! converts.isEmpty());
        
        // A convert value is an EclipseLink extension and it takes precedence
        // over all JPA converters so check for it first.
        if (convertValue != null && ! convertValue.equals(Convert.NONE)) {
            processConvert(mapping, convertValue, referenceClass, isForMapKey, hasConverts);
        } else if (hasConverts) {
            // If we have JPA converts, apply them.
            processConverts(converts, mapping, referenceClass, isForMapKey);
        } else if (getProject().hasAutoApplyConverter(referenceClassWithGenerics)) {
            // If no convert is specified and there exist an auto-apply
            // converter for the reference class, apply it.
            getProject().getAutoApplyConverter(referenceClassWithGenerics).process(mapping, isForMapKey, null);
        } else {
            // Check for original JPA converters. Check for an enum first since 
            // it will fall into a serializable mapping otherwise since enums 
            // are serialized.
            if (isEnumerated(referenceClass, isForMapKey)) {
                processEnumerated(getEnumerated(isForMapKey), mapping, referenceClass, isForMapKey);
            } else if (isLob(referenceClass, isForMapKey)) {
                processLob(getLob(isForMapKey), mapping, referenceClass, isForMapKey);
            } else if (isTemporal(referenceClass, isForMapKey)) {
                processTemporal(getTemporal(isForMapKey), mapping, referenceClass, isForMapKey);
            } else if (isSerialized(referenceClass, isForMapKey)) {
                processSerialized(mapping, referenceClass, isForMapKey);
            }
        }
    }
    
    /**
     * INTERNAL:
     * Process a mapping key converter either from an EclipseLink convert
     * specification or a JPA converter specification (map-key-convert, 
     * map-key-temporal, map-key-enumerated) to be applied to the given mapping.
     */
    protected void processMappingKeyConverter(DatabaseMapping mapping, String convertValue, List<ConvertMetadata> converts, MetadataClass referenceClass, MetadataClass referenceClassWithGenerics) {
        processMappingConverter(mapping, convertValue, getMapKeyConverts(converts), referenceClass, referenceClassWithGenerics, true);
    }
    
    /**
     * INTERNAL:
     * Process a convert value which specifies the name of an EclipseLink
     * converter to process with this accessor's mapping.
     */
    protected void processMappingValueConverter(DatabaseMapping mapping, String convertValue, List<ConvertMetadata> converts, MetadataClass referenceClass, MetadataClass referenceClassWithGenerics) {
        processMappingConverter(mapping, convertValue, getConverts(converts), referenceClass, referenceClassWithGenerics, false);
    }

    /**
     * INTERNAL:
     * Process the join columns for the owning side of a one to one mapping.
     * The default pk and fk field names are used only with single primary key 
     * entities. The processor should never get as far as to use them with 
     * entities that have a composite primary key (validation exception will be 
     * thrown).
     */
    protected void processForeignKeyRelationship(ForeignReferenceMapping mapping, List<JoinColumnMetadata> joinColumns, ForeignKeyMetadata foreignKey, MetadataDescriptor referenceDescriptor, String defaultFKFieldName, DatabaseTable defaultFKTable) {
        // We need to know if all the mappings are read-only so we can determine 
        // if we use target foreign keys to represent read-only parts of the 
        // join, or if we simply set the whole mapping as read-only
        boolean allReadOnly = true;
        Map<DatabaseField, DatabaseField> fields = new HashMap<DatabaseField, DatabaseField>();
        
        // Build our fk->pk associations.
        for (JoinColumnMetadata joinColumn : joinColumns) {
            // Look up the primary key field from the referenced column name.
            DatabaseField pkField = getReferencedField(joinColumn.getReferencedColumnName(), referenceDescriptor, MetadataLogger.PK_COLUMN);
            
            // The foreign key should be built using the primary key field 
            // since it will contain extra metadata that can not be specified
            // in the join column. This will keep the pk and fk fields in sync.
            DatabaseField fkField = joinColumn.getForeignKeyField(pkField);
            setFieldName(fkField, defaultFKFieldName, MetadataLogger.FK_COLUMN);
            
            // Set the table name if one is not already set.
            if (! fkField.hasTableName()) {
                fkField.setTable(defaultFKTable);
            }
            
            fields.put(fkField, pkField);
            allReadOnly = allReadOnly && fkField.isReadOnly();
        }
        
        DatabaseTable foreignKeyTable = null;
        
        // Apply the fields to the mapping based on what we found.
        for (DatabaseField fkField : fields.keySet()) {
            DatabaseField pkField = fields.get(fkField);
            
            if (allReadOnly || ! fkField.isReadOnly()) {
                // Add a source foreign key to the mapping.
                mapping.addForeignKeyField(fkField, pkField);
            } else {
                // This is a read-only join column that is part of a set of join 
                // columns that are not all read only - hence this is not a 
                // read-only mapping, but instead uses a target foreign key 
                // field to enable the read-only functionality
                mapping.addTargetForeignKeyField(pkField, fkField);
            }
            
            // Set the foreign key table to the first fk's table.
            if (foreignKeyTable == null) {
                foreignKeyTable = fkField.getTable();
            }
        }
        
        // If all the join columns are read-only then set the mapping as read only.
        mapping.setIsReadOnly(allReadOnly);
        
        // Process the foreign key if one is provided. Right now this assumes
        // and supports the foreign keys all being on the same table. It covers
        // the spec case (on the source table) and our extended support when 
        // the fk's are on the target table as well.
        if (foreignKey != null) {
            foreignKey.process(foreignKeyTable);
        }
    }
    
    /**
     * INTERNAL:
     * Adds properties to the mapping.
     */
    protected void processProperties(DatabaseMapping mapping) {
        // If we were loaded from XML use the properties loaded from there
        // only. Otherwise look for annotations.
        if (loadedFromXML()) {
            for (PropertyMetadata property : getProperties()) {
                processProperty(mapping, property);
            }
        } else {
            // Look for annotations.
            MetadataAnnotation properties = getAnnotation(Properties.class);
            if (properties != null) {
                for (Object property : properties.getAttributeArray("value")) {
                    processProperty(mapping, new PropertyMetadata((MetadataAnnotation) property, this));
                }
            }
            
            MetadataAnnotation property = getAnnotation(Property.class);
            if (property != null) {
                processProperty(mapping, new PropertyMetadata(property, this));
            }    
        }
    }
    
    /**
     * INTERNAL:
     * Adds properties to the mapping. They can only come from one place,
     * therefore if we add the same one twice we know to throw an exception.
     */
    protected void processProperty(DatabaseMapping mapping, PropertyMetadata property) {
        if (property.shouldOverride(m_properties.get(property.getName()))) {
            m_properties.put(property.getName(), property); 
            mapping.addUnconvertedProperty(property.getName(), property.getValue(), getJavaClassName(property.getValueType()));
        }
    }
    
    /**
     * INTERNAL:
     * Subclasses should call this method if they want the warning message or
     * override the method if they want/support different behavior.
     * @see BasicAccessor
     */
    protected void processReturnInsert() {
        if (hasReturnInsert()) {
            getLogger().logWarningMessage(MetadataLogger.IGNORE_RETURN_INSERT_ANNOTATION, getAnnotatedElement());
        }
    }    
    
    /**
     * INTERNAL:
     * Subclasses should call this method if they want the warning message.
     */
    protected void processReturnInsertAndUpdate() {
        processReturnInsert();
        processReturnUpdate();
    }
    
    /**
     * INTERNAL:
     * Subclasses should call this method if they want the warning message or
     * override the method if they want/support different behavior.
     * @see BasicAccessor
     */
    protected void processReturnUpdate() {
        if (hasReturnUpdate()) {
            getLogger().logWarningMessage(MetadataLogger.IGNORE_RETURN_UPDATE_ANNOTATION, getAnnotatedElement());
        }
    }
    
    /**
     * INTERNAL:
     * Process a potential serializable attribute. If the class implements 
     * the Serializable interface then set a SerializedObjectConverter on 
     * the mapping.
     */
    protected void processSerialized(DatabaseMapping mapping, MetadataClass referenceClass, boolean isForMapKey) {        
        new SerializedMetadata(this).process(mapping, this, referenceClass, isForMapKey);
    }
    
    /**
     * INTERNAL:
     * Process a potential serializable attribute. If the class implements 
     * the Serializable interface then set a SerializedObjectConverter on 
     * the mapping.
     */
    protected void processSerialized(DatabaseMapping mapping, MetadataClass referenceClass, MetadataClass classification, boolean isForMapKey) {
        new SerializedMetadata(this).process(mapping, this, referenceClass, classification, isForMapKey);
    }
    
    /**
     * INTERNAL:
     * Process a temporal type accessor.
     */
    protected void processTemporal(TemporalMetadata temporal, DatabaseMapping mapping, MetadataClass referenceClass, boolean isForMapKey) {
        if (temporal == null) {
            // We need to have a temporal type on either a basic mapping or the key to a collection
            // mapping. Since the temporal type was not specified, per the JPA spec we *should* throw an
            // exception.. but lets be a little nice to our users and default to timestamp.
            MetadataAnnotation annotation = new MetadataAnnotation();
            annotation.setName("javax.persistence.Temporal");
            annotation.addAttribute("value", "TIMESTAMP");
            temporal = new TemporalMetadata(annotation, this);

            // This call handles both @Temporal and @MapKeyTemporal
            setTemporal(temporal, isForMapKey);
        }
        
        temporal.process(mapping, this, referenceClass, isForMapKey);
    }
    
    /**
     * INTERNAL:
     * Set the getter and setter access methods for this accessor.
     */
    protected void setAccessorMethods(DatabaseMapping mapping) {
        if (usesPropertyAccess() || usesVirtualAccess()) {
            if (usesVirtualAccess()) {
                mapping.setAttributeAccessor(new VirtualAttributeAccessor());
            }
            
            mapping.setGetMethodName(getGetMethodName());
            mapping.setSetMethodName(getSetMethodName());
        }
    }
    
    /**
     * INTERNAL:
     * Used for OX mapping.
     */
    public void setAttributeType(String attributeType) {
        m_attributeType = attributeType;
    }
    
    /**
     * INTERNAL:
     * Sets the class accessor for this mapping accessor.
     */
    public void setClassAccessor(ClassAccessor classAccessor) {
        m_classAccessor = classAccessor;
    }

    /**
     * INTERNAL:
     * Used for OX mapping.
     */
    public void setField(ColumnMetadata column) {
        m_field = column;
    }
    
    /** 
     * INTERNAL:
     * Set the correct indirection policy on a collection mapping. Method
     * assume that the reference class has been set on the mapping before
     * calling this method.
     */
    protected void setIndirectionPolicy(ContainerMapping mapping, String mapKey, boolean usesIndirection) {
        MetadataClass rawClass = getRawClass();
        boolean containerPolicySet = false;
        if (usesIndirection && (mapping instanceof ForeignReferenceMapping)) {
            containerPolicySet = true;
            CollectionMapping collectionMapping = (CollectionMapping)mapping;
            if (rawClass.equals(Map.class)) {
                if (collectionMapping.isDirectMapMapping()) {
                    ((DirectMapMapping) mapping).useTransparentMap();
                } else {
                    collectionMapping.useTransparentMap(mapKey);
                }
            } else if (rawClass.equals(List.class)) {
                collectionMapping.useTransparentList();
            } else if (rawClass.equals(Collection.class)) {
                collectionMapping.useTransparentCollection();
            } else if (rawClass.equals(Set.class)) {
                collectionMapping.useTransparentSet();
            } else {
                getLogger().logWarningMessage(MetadataLogger.WARNING_INVALID_COLLECTION_USED_ON_LAZY_RELATION, getJavaClass(), getAnnotatedElement(), rawClass);
                processIndirection((ForeignReferenceMapping)mapping);
                containerPolicySet = false;
            }
        } else {
            if (mapping instanceof CollectionMapping) {
                ((CollectionMapping)mapping).dontUseIndirection();
            }
        }
        if (!containerPolicySet) {            
            if (rawClass.equals(Map.class)) {
                if (mapping instanceof DirectMapMapping) {
                    ((DirectMapMapping) mapping).useMapClass(java.util.Hashtable.class);
                } else {
                    mapping.useMapClass(java.util.Hashtable.class, mapKey);
                }
            } else if (rawClass.equals(Set.class)) {
                // This will cause it to use a CollectionContainerPolicy type
                mapping.useCollectionClass(java.util.HashSet.class);
            } else if (rawClass.equals(List.class)) {
                // This will cause a ListContainerPolicy type to be used or 
                // OrderedListContainerPolicy if ordering is specified.
                mapping.useCollectionClass(java.util.Vector.class);
            } else if (rawClass.equals(Collection.class)) {
                // Force CollectionContainerPolicy type to be used with a 
                // collection implementation.
                mapping.setContainerPolicy(new CollectionContainerPolicy(java.util.Vector.class));
            } else {
                // Use the supplied collection class type if its not an interface
                if (mapKey == null || mapKey.equals("")){
                    if (rawClass.isList()) {
                        mapping.useListClassName(rawClass.getName());
                    } else {
                        mapping.useCollectionClassName(rawClass.getName());
                    }
                } else {
                    mapping.useMapClassName(rawClass.getName(), mapKey);
                }
            }
        }
    }
    
    /**
     * INTERNAL:
     * This will do three things:
     * 1 - process any common level metadata for all mappings.
     * 2 - add the mapping to the internal descriptor.
     * 3 - store the actual database mapping associated with this accessor.
     * 
     * Calling this method is a must for all mapping accessors since it will 
     * help to:
     * 1 - determine if the accessor has been processed, and
     * 2 - sub processing will may need access to the mapping to set its 
     *     metadata.
     */
    protected void setMapping(DatabaseMapping mapping) {
        if (! isMultitenantId()) {
            // Before adding the mapping to the descriptor, process the 
            // properties for this mapping (if any). Avoid this is we are
            // multitenant id accessor which is derived from primary key
            // tenant discriminator columns on the class where any properties
            // defined there do not apply to this mapping.
            processProperties(mapping);
        }
        
        // Add the mapping to the class descriptor.
        getDescriptor().getClassDescriptor().addMapping(mapping);
        
        // Keep a reference back to this mapping for quick look up.
        m_mapping = mapping;  
    }
    
    /**
     * INTERNAL:
     * An override mapping is created when an association override is specified
     * to a relationship accessor on an embeddable class. For any non-owning 
     * relationship accessor referring to this accessor will need its override
     * mapping and not the original mapping from the embeddable so that it can
     * populate the right metadata.
     */
    protected void setOverrideMapping(DatabaseMapping mapping) {
        m_overrideMapping = mapping;
    }
    
    /**
     * INTERNAL:
     */
    @Override
    public String toString() {
        return getAnnotatedElementName();
    }
    
    /**
     * INTERNAL:
     * Update the primary key field on the owning descriptor the override field
     * given.
     */
    protected void updatePrimaryKeyField(MappingAccessor idAccessor, DatabaseField overrideField) {
        getOwningDescriptor().removePrimaryKeyField(idAccessor.getMapping().getField());
        getOwningDescriptor().addPrimaryKeyField(overrideField, idAccessor);
    }
    
    /**
     * INTERNAL:
     * @see RelationshipAccessor
     * @see DirectAccessor
     */
    protected boolean usesIndirection() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Returns true if this mapping or class uses property access. In an 
     * inheritance hierarchy, the subclasses inherit their access type from 
     * the parent (unless there is an explicit access setting).
     */
    public boolean usesPropertyAccess() {
        if (hasAccess()) {
            return getAccess().equals(JPA_ACCESS_PROPERTY);
        } else {
            return hasAccessMethods() ? !usesVirtualAccess() : m_classAccessor.usesPropertyAccess();
        }
    }
    
    /**
     * INTERNAL:
     * Returns true if this mapping or class uses virtual access. In an 
     * inheritance hierarchy, the subclasses inherit their access type from 
     * the parent (unless there is an explicit access setting).
     */
    public boolean usesVirtualAccess() {
        if (hasAccess()) {
            return getAccess().equals(EL_ACCESS_VIRTUAL);
        } else {
            return m_classAccessor.usesVirtualAccess();
        }
    }

    /**
     * INTERNAL:
     * Returns true if this mapping or class uses property field. In an 
     * inheritance hierarchy, the subclasses inherit their access type from 
     * the parent (unless there is an explicit access setting).
     */
    public boolean usesFieldAccess() {
        if (hasAccess()) {
            return getAccess().equals(JPA_ACCESS_FIELD);
        } else {
            return m_classAccessor.usesFieldAccess();
        }
    }
}
