/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the "License").  You may not use this file except 
 * in compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * glassfish/bootstrap/legal/CDDLv1.0.txt or 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html. 
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * HEADER in each file and include the License file at 
 * glassfish/bootstrap/legal/CDDLv1.0.txt.  If applicable, 
 * add the following below this CDDL HEADER, with the 
 * fields enclosed by brackets "[]" replaced with your 
 * own identifying information: Portions Copyright [yyyy] 
 * [name of copyright owner]
 */
package oracle.toplink.essentials.internal.ejb.cmp3.metadata;

import java.util.Map;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.StringTokenizer;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import oracle.toplink.essentials.descriptors.ClassDescriptor;

import oracle.toplink.essentials.internal.helper.Helper;
import oracle.toplink.essentials.internal.helper.DatabaseField;
import oracle.toplink.essentials.internal.helper.DatabaseTable;

import oracle.toplink.essentials.internal.sessions.AbstractSession;

import oracle.toplink.essentials.mappings.DatabaseMapping;
import oracle.toplink.essentials.mappings.OneToOneMapping;
import oracle.toplink.essentials.mappings.OneToManyMapping;
import oracle.toplink.essentials.mappings.ManyToManyMapping;
import oracle.toplink.essentials.mappings.CollectionMapping;
import oracle.toplink.essentials.mappings.DirectToFieldMapping;
import oracle.toplink.essentials.mappings.AggregateObjectMapping;
import oracle.toplink.essentials.mappings.ForeignReferenceMapping;

import oracle.toplink.essentials.mappings.converters.EnumTypeConverter;
import oracle.toplink.essentials.mappings.converters.TypeConversionConverter;
import oracle.toplink.essentials.mappings.converters.SerializedObjectConverter;

/**
 * Common metatata processor for the annotation and xml processors.
 * 
 * @author Guy Pelletier, Dave McCann
 * @since TopLink EJB 3.0 Reference Implementation
 */
public abstract class MetadataProcessor {
    protected ClassLoader m_loader;
    protected MetadataLogger m_logger;
    protected MetadataValidator m_validator;
    protected AbstractSession m_session;
    
    // Metadata holder for descriptors we need to hold onto during processing
    protected HashMap m_metadataDescriptors;	
    
    // Boolean to specify if we should weave for value holders.
    protected boolean m_enableLazyForOneToOne;
    
    // Entities we need to go through a second time, that have relationships.
    protected HashSet m_relatedEntities;
    
    /**
     * INTERNAL:
     * Method will add a join column details to the vector passed in if it is 
     * empty. Will set the table names, but will leave the field names blank so 
     * that they go through the appropriate defaulting.
     */
    protected void addJoinColumnDefault(List joinColumns, String sourceTableName, String targetTableName, MetadataDescriptor md) {            
        if (joinColumns.isEmpty()) {
            MetadataJoinColumn mjc = new MetadataJoinColumn();
            mjc.getPrimaryKeyField().setTableName(sourceTableName);
            mjc.getForeignKeyField().setTableName(targetTableName);
            // Flag the metadata descriptor if a default table is being used.
            md.handlePotentialDefaultTableUsage(mjc.getForeignKeyField());
        	joinColumns.add(mjc);
        } 
    }
	
    /**
     * INTERNAL:
     * Add the relation key fields to a many to many mapping.
     */
    protected void addManyToManyRelationKeyFields(List relationKeys, ManyToManyMapping mapping, MetadataAccessor accessor, String defaultFieldName, MetadataDescriptor md, boolean isSource) {
        // Validate the primary key join columns we found.
        if (md.hasCompositePrimaryKey()) {
            // Composite primary key, validate all join columns are specified.
            if (relationKeys.size() != md.getPrimaryKeyFields().size()) {
                getValidator().throwIncompleteJoinColumnsSpecified(accessor.getJavaClass(), accessor.getAnnotatedElement());
            } 
        } else {
            // Single primary key relationship, add a defaulting join column if needed.
            addJoinColumnDefault(relationKeys, md.getPrimaryTableName(), mapping.getRelationTableQualifiedName(), md);
        }

        // Set the right context level.
        String PK_CTX, FK_CTX;
        if (isSource) {
            PK_CTX = MetadataLogger.SOURCE_PK_COLUMN;
            FK_CTX = MetadataLogger.SOURCE_FK_COLUMN;
        } else {
            PK_CTX = MetadataLogger.TARGET_PK_COLUMN;
            FK_CTX = MetadataLogger.TARGET_FK_COLUMN;
        }
        
        //  Only mappings that 'should' be complete. 
        if (! handlePotentialPartialManyToManyRelationshipMapping(mapping, isSource, relationKeys)) {
            // Add all the relation keys we found to the mapping.
            for (Iterator iterator = relationKeys.iterator(); iterator.hasNext();) {
                MetadataJoinColumn mjc = (MetadataJoinColumn) iterator.next();
        	
                // If the pk field (referencedColumnName) is not specified, it 
                // defaults to the primary key of the referenced table.
                String defaultPKFieldName = md.getPrimaryKeyFieldName();
                DatabaseField pkField = mjc.getPrimaryKeyField();
            
                // The mapping may have been partially completed during XML 
                // processing. If so, set the pk field (either "" indicating to 
                // default, or an actual value). This will only apply to inverse 
                // join columns
                if (mapping.requiresCompletion() && !isSource) {
                    pkField.setName(mapping.getXMLPKNameAttribute());
                }
            
                pkField.setName(getName(pkField, defaultPKFieldName, PK_CTX, accessor));

                // If the fk field (name) is not specified, it defaults to the 
                // name of the referencing relationship property or field of the 
                // referencing entity + "_" + the name of the referenced primary 
                // key column. If there is no such referencing relationship 
                // property or field in the entity (i.e., a join table is used), 
                // the join column name is formed as the concatenation of the 
                // following: the name of the entity + "_" + the name of the 
                // referenced primary key column.
                DatabaseField fkField = mjc.getForeignKeyField();
                String defaultFKFieldName = defaultFieldName + "_" + defaultPKFieldName;
        	
                // The mapping may have been partially completed during XML 
                // processing. If so, set the fk field (either "" indicating to 
                // default, or an actual value). This will only apply to inverse 
                // join columns
                if (mapping.requiresCompletion() && !isSource) {
                    fkField.setName(mapping.getXMLFKNameAttribute());
                }
            
                fkField.setName(getName(fkField, defaultFKFieldName, FK_CTX, accessor));
                
                // Add a target relation key to the mapping.
                if (isSource) {
                    mapping.addSourceRelationKeyField(fkField, pkField);
                } else {
                    mapping.addTargetRelationKeyField(fkField, pkField);
                }
            }
        }
    }
    
    /**
     * INTERNAL:
	 * Add multiple fields to the descriptor. Called from either @Inheritance 
     * or @SecondaryTable context.
	 */
    protected void addMultipleTableKeyField(MetadataJoinColumn mjc, String PK_CTX, String FK_CTX, MetadataDescriptor md) {
        // In an inheritance case this call will return the pk field on the
        // root class of the inheritance hierarchy. Otherwise in a secondary
        // table case it's the primary key field name off our own descriptor.
        String defaultPKFieldName = md.getPrimaryKeyFieldName();

        DatabaseField pkField = mjc.getPrimaryKeyField();
        pkField.setName(getName(pkField, defaultPKFieldName, PK_CTX, md));

        DatabaseField fkField = mjc.getForeignKeyField();
        fkField.setName(getName(fkField, pkField.getName(), FK_CTX, md));
        
        if (fkField.getName().equals(pkField.getName())) {
            // Add a multiple table primary key to the descriptor.
            md.addMultipleTablePrimaryKeyField(pkField, fkField);
        } else {
            // Add a multiple table foreign key to the descriptor.
            md.addMultipleTableForeignKeyField(pkField, fkField);
        }
    }
    
    /**
     * INTERNAL:
     * Return a join table that is, a DatabaseTable object.
     */
    protected DatabaseTable buildJoinTable(MetadataAccessor accessor) {
        return buildJoinTable("", "", "", accessor);
    }
    
    /**
     * INTERNAL:
     * Return a join table that is, a DatabaseTable object.
     */
    protected DatabaseTable buildJoinTable(String name, String catalog, String schema, MetadataAccessor accessor) {
        // Build the default table name
        String sourceName = Helper.getShortClassName(accessor.getJavaClassName()).toUpperCase();
        String targetName = Helper.getShortClassName(accessor.getReferenceClassName()).toUpperCase();
        String defaultName = sourceName + "_" + targetName; 
        
        // Build the join table with what we know.
        Object element = accessor.getAnnotatedElement();
        
        // Name could be "", need to check against the default name.
		name = getName(name, defaultName, MetadataLogger.JOIN_TABLE_NAME, element);

        return buildTable(name, catalog, MetadataLogger.JOIN_TABLE_CATALOG, schema, MetadataLogger.JOIN_TABLE_SCHEMA, element, accessor.getMetadataDescriptor());
    }
    
    /**
	 * INTERNAL:
	 * Helper method to return a database table object from candidate table, 
     * catalog and schema names.
	 */
    protected DatabaseTable buildTable(String name, String catalog, String schema, MetadataDescriptor md) {
        // Name could be "", need to check against the default name.
		name = getName(name, md.getDefaultTableName(), MetadataLogger.TABLE_NAME, md.getJavaClass());

        return buildTable(name, catalog, MetadataLogger.TABLE_CATALOG, schema, MetadataLogger.TABLE_SCHEMA, md.getJavaClass(), md);
    }
    
    /**
	 * INTERNAL:
	 * Helper method to return a database table object from candidate table, 
     * catalog and schema names.
	 */
    protected DatabaseTable buildTable(String name, String catalog, String catalogContext, String schema, String schemaContext, Object element, MetadataDescriptor md) {
        // Catalog could be "", need to check for an XML default.
        catalog = getName(catalog, md.getCatalog(), catalogContext, element);
        
        // Schema could be "", need to check for an XML default.
        schema = getName(schema, md.getSchema(), schemaContext, element);
		
		// Append the catalog and schema to the tableName
		return new DatabaseTable(MetadataHelper.getFullyQualifiedTableName(name, catalog, schema));
    }
    
    /**
     * INTERNAL:
     */
     protected DatabaseField getDatabaseField(MetadataAccessor accessor) {
         // Check if we have an attribute override first, otherwise process
         // for a column.
        if (accessor.getMetadataDescriptor().hasAttributeOverrideFor(accessor)) {
            return accessor.getMetadataDescriptor().getAttributeOverrideFor(accessor);
        } else {
            return processColumn(accessor);
        }
     }
    
    /** 
     * INTERNAL:
	 * Return the logger used by the processor.
	 */
	public abstract MetadataLogger getLogger();
	
    /**
	 * INTERNAL:
	 */
    public abstract MetadataDescriptor getMetadataDescriptor(Class cls);
    
    /**
     * INTERNAL:
     * Helper method to return a field name from a candidate field name and a 
     * default field name.
     * 
     * This method will typically be called when setting the name of a field
     * that is involved in a relationship - i.e. during join table, join column, 
     * and inverse join column processing.  If the default name is used, flag
     * the descriptor wrapper if the default name contains a defaulted primary
     * key field name.  This will be rectified during annotation processing.
     * 
     * Requires the context from where this method is called to output the 
     * correct logging message when defaulting the field name.
     */
    protected String getName(DatabaseField field, String defaultName, String context, MetadataAccessor accessor) {
        String name = field.getName();
    	// Check if a candidate was specified otherwise use the default.
        if (name != null && !name.equals("")) {
            return name;
        } else if (defaultName == null || defaultName.equals("")) {
            return name;
        } else {
            // Log the defaulting field name based on the given context.
            getLogger().logConfigMessage(context, accessor.getAnnotatedElement(), defaultName);

            handlePotentialDefaultPrimaryKeyUsage(field, accessor, defaultName, context);

            return defaultName;
        }
    }

    /**
     * INTERNAL:
     * Helper method to return a field name from a candidate field name and a 
     * default field name.
     * 
     * This method will typically be called when setting the name of a field
     * that is involved in a multiple table scenario, i.e. in the case of 
     * inheritance using a JOINED strategy, or where a secondary table is 
     * used.  If the default name is used, flag the descriptor wrapper if the 
     * default name contains a defaulted primary key field name.  This will 
     * be rectified during annotation processing.
     * 
     * Requires the context from where this method is called to output the 
     * correct logging message when defaulting the field name.
     */
    protected String getName(DatabaseField field, String defaultName, String context, MetadataDescriptor md) {
    	String name = field.getName();
        // Check if a candidate was specified otherwise use the default.
        if (name != null && !name.equals("")) {
            return name;
        } else if (defaultName == null || defaultName.equals("")) {
            return name;
        } else {
            // Log the defaulting field name based on the given context.
            getLogger().logConfigMessage(context, md.getJavaClass(), defaultName);
            
            if (defaultName.equals(md.getPrimaryKeyFieldName())) {
            	handlePotentialDefaultPrimaryKeyUsage(field, md);
            }
            
            return defaultName;
        }
	}
    
    /**
     * INTERNAL:
     * Helper method to return a field name from a candidate field name and a 
     * default field name.
     * 
     * Requires the context from where this method is called to output the 
     * correct logging message when defaulting the field name.
     *
     * In some cases, both the name and defaultName could be "" or null,
     * therefore, don't log any message and return name.
     */
    protected String getName(String name, String defaultName, String context, Object element) {
        // Check if a candidate was specified otherwise use the default.
        if (name != null && !name.equals("")) {
            return name;
        } else if (defaultName == null || defaultName.equals("")) {
            return name;
        } else {
            // Log the defaulting field name based on the given context.
            getLogger().logConfigMessage(context, element, defaultName);
            return defaultName;
        }
	}
    
    /**
     * INTERNAL:
     * Method to return an owner mapping. It will tell the owner class to
     * process itself it hasn't already done so.
     */
    protected DatabaseMapping getOwningMapping(MetadataAccessor accessor, String ownerAttributeName) {
        MetadataDescriptor ownerDmd = accessor.getReferenceMetadataDescriptor();
        DatabaseMapping mapping = ownerDmd.getMappingForAttributeName(ownerAttributeName);
        
        // If no mapping was found, there is an error in the mappedBy field, 
        // therefore, throw an exception. Allow processing of relationship 
        // mappings where the target-entity is not defined in XML.
        
        if (mapping == null) {
            getValidator().throwNoMappedByAttributeFound(ownerDmd.getJavaClass(), ownerAttributeName, accessor.getJavaClass(), accessor.getAttributeName());
        }
        
        return mapping;
    }
    
    /** 
     * INTERNAL:
	 * Return the validator used by the processor.
	 */
	protected abstract MetadataValidator getValidator();
     
    /**
     * INTERNAL:
     */
    protected abstract void handlePotentialDefaultPrimaryKeyUsage(DatabaseField dbField, MetadataAccessor accessor, String defaultName, String context);
    
    /**
     * INTERNAL:
     */
    protected abstract void handlePotentialDefaultPrimaryKeyUsage(DatabaseField dbField, MetadataDescriptor md);
    
    /**
     * INTERNAL:
     * @See EntityMappingsXMLProcessor.
     */
    abstract protected boolean handlePotentialPartialManyToManyRelationshipMapping(ManyToManyMapping mapping, boolean isSource, List relationKeys);
    
    /**
     * INTERNAL:
     * @See EntityMappingsXMLProcessor.
     */
    abstract protected boolean handlePotentialPartialOneToOneRelationshipMapping(OneToOneMapping mapping, List joinColumns);

    /**
     * INTERNAL:
     * Initialize a OneToOneMapping.
     */
    protected OneToOneMapping initOneToOneMapping(MetadataAccessor accessor, boolean usesIndirection, Object[] cascadeTypes, boolean isOptional) {
        // the mapping may have been partially defined in XML, and hence
    	// partially processed - if the mapping already exists, return it
    	DatabaseMapping existingMapping = accessor.getMetadataDescriptor().m_descriptor.getMappingForAttributeName(accessor.getAttributeName());
    	if (existingMapping != null) {
    		return (OneToOneMapping)existingMapping; 
    	}

    	OneToOneMapping mapping = new OneToOneMapping();
        mapping.setIsReadOnly(false);
        mapping.setIsPrivateOwned(false);
        mapping.setIsOptional(isOptional);
        mapping.setAttributeName(accessor.getAttributeName());
        mapping.setReferenceClassName(accessor.getReferenceClassName());
        
        // If the global weave for value holders is true, the use the value
        // from usesIndirection. Otherwise, force it to false.
        usesIndirection = (m_enableLazyForOneToOne) ? usesIndirection : false;
        mapping.setUsesIndirection(usesIndirection);
        
        // Set the getter and setter methods if access is PROPERTY and the
        // mapping doesn't use indirection.
        setAccessorMethods(accessor, mapping);
        
        processCascadeType(accessor, cascadeTypes, mapping);
        return mapping;
    } 

    /**
     * INTERNAL:
     */
    protected void populateCollectionMapping(CollectionMapping mapping, MetadataAccessor accessor, Class targetEntity, Object[] cascadeTypes, boolean usesIndirection) {
        mapping.setIsReadOnly(false);
        mapping.setIsPrivateOwned(false);
        mapping.setAttributeName(accessor.getAttributeName());
        
        // Will check for PROPERTY access
        setAccessorMethods(accessor, mapping);

        processCascadeType(accessor, cascadeTypes, mapping);
        
        // Figure out the referenceClass (from targetEntity) and set on the mapping.
        accessor.setReferenceClass(targetEntity, MetadataLogger.ONE_TO_MANY_MAPPING_REFERENCE_CLASS);
        mapping.setReferenceClassName(accessor.getReferenceClassName());
        
        // Process an OrderBy id there is one.
        processOrderBy(accessor, mapping);
        
        // Process a MapKey if there is one.
        String mapKey = processMapKey(accessor, mapping);
        
        // Set the correct indirection on the collection mapping.
        // ** Note the reference class or reference class name needs to be set 
        // on the mapping before setting the indirection policy.
        MetadataHelper.setIndirectionPolicy(usesIndirection, mapping, accessor.getRawClass(), mapKey);
    }
    
    /**
     * INTERNAL:
     */
    protected abstract void populateCollectionMapping(CollectionMapping mapping, Object element, MetadataAccessor accessor);
    
    /**
     * INTERNAL:
     */
    protected abstract void preProcessGeneratedValue(Object generatedValue, MetadataAccessor metadataAccessor, DatabaseField field, MetadataDescriptor md);
    
    /**
     * INTERNAL:
     */
    protected abstract void preProcessSequencing(MetadataAccessor accessor);
    
    /**
     * INTERNAL:
     * Process an accessor method (annotations only) or field. Relationship 
     * accessors will be stored for later processing.
     */
    protected void processAccessor(MetadataAccessor accessor) {
        // Store the accessor for later retrieval.
        accessor.store();
        
        // Preprocess the sequencing annotations/xml elements.
        preProcessSequencing(accessor);        
        
        // Now look for the other annotations/elements that could be present.
        if (accessor.isEmbedded()) {
            // Process an @Embedded or embedded element
            processEmbedded(accessor, false);
        } else if (accessor.isEmbeddedId()) {
            // Process an @EmbeddedId or embedded-id element
            processEmbeddedId(accessor);
        } else if (accessor.isRelationship()) { 
            // Store the relationship accessors for later processing.
            m_relatedEntities.add(accessor.getMetadataDescriptor());
        } else {
            // Process the @Column or column element if there is one.
            DatabaseField field = getDatabaseField(accessor);
             
            // flag the metadata descriptor if a default table is being used 
            accessor.getMetadataDescriptor().handlePotentialDefaultTableUsage(field); 
            
            // Process an @Version or version element if there is one.
            if (accessor.isVersion()) {
                if (accessor.getMetadataDescriptor().usesOptimisticLocking()) {
                    // Project XML merging. XML wins, ignore annotations/orm xml
                    getLogger().logWarningMessage(MetadataLogger.IGNORE_VERSION_LOCKING, accessor);
                } else {
	            field.setType(accessor.getRawClass());                        
                    accessor.getMetadataDescriptor().setOptimisticLockingPolicy(field);
                }
            } else {
                // Process an @Id or id element if there is one.
                processId(accessor, field);
            }
                
            if (accessor.getMetadataDescriptor().hasMappingForAccessor(accessor)) {
                // Project XML merging. XML wins, ignore annotations/orm xml
                getLogger().logWarningMessage(MetadataLogger.IGNORE_MAPPING, accessor);
            } else {
                // Process a DirectToFieldMapping, that is a Basic that could
                // be used in conjunction with a Lob, Temporal, Enumerated
                // or inferred to be used with a serialized mapping.
                processDirectToFieldMapping(accessor, field);
            }
        }
    }

    /**
     * INTERNAL:
     */
    protected abstract void processAccessors(MetadataDescriptor md);

    /**
     * INTERNAL:
     */
    protected abstract void processAssociationOverrides(MetadataAccessor accessor, AggregateObjectMapping mapping);
    
	/**
     * INTERNAL:
     * Process an @AttributeOverride or attribute-override element for 
     * an embedded object, that is, an aggregate object mapping in TopLink.
	 */
	protected void processAttributeOverride(String name, Object column, MetadataAccessor accessor, AggregateObjectMapping mapping) {
    	MetadataDescriptor md = accessor.getMetadataDescriptor();
        MetadataDescriptor aggregateDmd = accessor.getReferenceMetadataDescriptor();
        
        // Set the attribute name on the aggregate.
        DatabaseMapping aggregateMapping = aggregateDmd.getMappingForAttributeName(name);
        
        if (aggregateMapping == null) {
            getValidator().throwInvalidEmbeddableAttribute(md.getJavaClass(), mapping.getAttributeName(), aggregateDmd.getJavaClass(), name);
        }
        
        // A sub-class to a mapped superclass may override an embedded
        // attribute override.
        DatabaseField field;
        if (md.hasAttributeOverrideFor(name)) {
            field = md.getAttributeOverrideFor(name);
        } else {
            field = processColumn(column, accessor.getAnnotatedElement(), accessor.getAttributeName(), aggregateDmd);
        }
        
        mapping.addFieldNameTranslation(field.getQualifiedName(), aggregateMapping.getField().getName());
	}
    
	/**
     * INTERNAL:
     * Process an @AttributeOverride or attribute-override element for an 
     * Entity (or MappedSuperclass) that inherits from an MappedSuperclass.
	 */
	protected void processAttributeOverride(String attributeName, Object column, MetadataDescriptor md) {
        md.addAttributeOverride(attributeName, processColumn(column, md.getJavaClass(), attributeName, md));
    }
        
    /**
     * INTERNAL:
     */
    protected abstract void processAttributeOverrides(MetadataAccessor accessor, AggregateObjectMapping mapping);
    
    /**
     * INTERNAL:
     */
    protected abstract void processBasic(MetadataAccessor accessor, DirectToFieldMapping mapping);

    /**
     * INTERNAL:
     */
    protected abstract void processCascadeType(MetadataAccessor accessor, Object[] cTypes, ForeignReferenceMapping mapping);

    /**
     * INTERNAL:
     */
    protected abstract DatabaseField processColumn(MetadataAccessor accessor);
    
    /**
     * INTERNAL:
     */
    protected abstract DatabaseField processColumn(Object column, Object annotatedElement, String attributeName, MetadataDescriptor md);
    
    /**
     * INTERNAL:
     * Process column details from an @Column or column element into a 
     * DatabaseField and return it.
     */
    protected DatabaseField processColumn(String attributeName, String fieldName, String columnDefinition, String table, boolean isUnique, boolean isNullable, boolean isInsertable, boolean isUpdatable, int length, int precision, int scale, Object element, MetadataDescriptor metadataDescriptor) {
    	DatabaseField dbField = new DatabaseField();
        dbField.setUnique(isUnique);
        dbField.setNullable(isNullable);
        dbField.setInsertable(isInsertable);
        dbField.setUpdatable(isUpdatable);
        dbField.setColumnDefinition(columnDefinition);
        dbField.setLength(length);
        dbField.setPrecision(precision);
        dbField.setScale(scale);

        String tableName = table.equals("") ? metadataDescriptor.getPrimaryTableName() : table; 
		setColumnDetails(dbField, tableName, fieldName, attributeName, element);
        
        return dbField;
	}
    
    /**
     * Add column defaults to a DatabaseField and return it.
     */
    protected DatabaseField processColumnDefaults(MetadataAccessor accessor) {
        return processColumnDefaults(accessor.getAttributeName(), accessor.getMetadataDescriptor(), accessor.getAttributeName());
    }

    /**
     * Add column defaults to a DatabaseField and return it.
     */
    protected DatabaseField processColumnDefaults(String attributeName, MetadataDescriptor md, Object element) {
        DatabaseField dbField = new DatabaseField();
		setColumnDetails(dbField, md.getPrimaryTableName(), "", attributeName, element);
        return dbField;
    }

	/**
	 * INTERNAL:
	 * Create and return a default database table object for the given
     * metadata descriptor.
	 */
	protected void processDefaultTable(MetadataDescriptor md) {
		processTable("", "", "", null, md);
	}
    
    /**
     * INTERNAL:
     * Process a Serialized or Basic into a DirectToFieldMapping. If neither 
     * is found a DirectToFieldMapping is created regardless.
     */
    protected void processDirectToFieldMapping(MetadataAccessor accessor, DatabaseField field) {        
        DirectToFieldMapping mapping = new DirectToFieldMapping();
        mapping.setField(field);
        mapping.setIsReadOnly(field.isReadOnly());
        mapping.setAttributeName(accessor.getAttributeName());
		
        // Will check for PROPERTY access
        setAccessorMethods(accessor, mapping);
        
        // Process a @Basic if there is one.
        processBasic(accessor, mapping);
        
        // Check for an enum first since it will fall into a serializable 
        // mapping otherwise (Enums are serialized)
        if (accessor.isEnumerated()) {
            processEnumerated(accessor, mapping);
        } else if (accessor.isLob()) {
            processLob(accessor, mapping);
        } else if (accessor.isTemporal()) {
            processTemporal(accessor, mapping);
        } else if (accessor.isSerialized()) {
            processSerialized(accessor, mapping);
        }
        
        // Add the mapping to the descriptor.
        accessor.getMetadataDescriptor().addMapping(mapping);
    }
    
    /**
     * INTERNAL:
	 * Process a discriminator column to set this class indicatior field name 
     * for inheritance.
	 */
	protected void processDiscriminatorColumn(String name, String columnDefinition, int length, String discriminatorType, MetadataDescriptor metadataDescriptor) {
        DatabaseField field = new DatabaseField();
        field.setTableName(metadataDescriptor.getPrimaryTableName());
        
        // Flag the metadata descriptor if a default table is being used.
        metadataDescriptor.handlePotentialDefaultTableUsage(field);
        
        field.setLength(length);
        field.setColumnDefinition(columnDefinition);
        field.setType(MetadataHelper.getDiscriminatorType(discriminatorType));
        
        // Set the name of the discriminator column.
        field.setName(getName(name, MetadataConstants.DEFAULT_DISCRIMINATOR_COLUMN, MetadataLogger.DISCRIMINATOR_COLUMN, metadataDescriptor.getJavaClass()));
        
        // Set the class indicator field on the inheritance policy.
        metadataDescriptor.setClassIndicatorField(field);
    }

	/**
	 * INTERNAL:
	 */
	protected abstract void processDiscriminatorValue(MetadataDescriptor md);
	
    /**
     * INTERNAL:
	 * Process a discriminator value to set the class indicator on the root 
     * descriptor of the inheritance hierarchy. 
     * 
     * If there is no discriminator value, the class indicator defaults to 
     * the class name.
	 */
	protected void processDiscriminatorValue(String discriminatorValue, MetadataDescriptor metadataDescriptor) {
        Class entityClass = metadataDescriptor.getJavaClass();
        
        if (! Modifier.isAbstract(entityClass.getModifiers())) {
            String classIndicator = Helper.getShortClassName(entityClass.getName());
        
            if (discriminatorValue != null) {
                classIndicator = discriminatorValue;
            }
        
            // Add the indicator to the inheritance root class' descriptor.
            metadataDescriptor.addClassIndicator(entityClass, classIndicator);
        }
    }
    
    /**
     * INTERNAL:
     * Process an embeddable class. If processing for an @EmbeddedId or 
     * embedded-id element, we must add the primary key field names to our 
     * processing entity.
     */
    protected MetadataDescriptor processEmbeddableClass(MetadataAccessor accessor, boolean isEmbeddedId) {
        Class referenceClass = accessor.getReferenceClass();
        MetadataDescriptor md = getMetadataDescriptor(referenceClass);
        MetadataDescriptor owningMd = accessor.getMetadataDescriptor();
        
        // Process this metadata descriptor it it hasn't already. 
        if (!md.isProcessed()) {
            md.setDescriptorIsEmbeddable();
            processAccessors(md);
        }
        
        // We need to set the primary keys on the owning descriptor metadata if 
        // this is an @EmbeddedId or embedded-id.
        if (isEmbeddedId && !md.ignoreIDAnnotations()) {
        	for (Iterator iterator = md.getMappings().iterator(); iterator.hasNext(); ) {
        		DatabaseMapping mapping = (DatabaseMapping) iterator.next();
                DatabaseField field = (DatabaseField) mapping.getField().clone();
                field.setTableName(owningMd.getPrimaryTableName());
                // flag the metadata descriptor if a default table is being used 
                owningMd.handlePotentialDefaultTableUsage(field);
                owningMd.addPrimaryKeyField(field);
            }
        }
        
        return md;
    }
    
    /**
     * INTERNAL:
     * Process an @Embedded or embedded element. Also called when processing 
     * an @EmbeddedId or embedded-id element.
     */    
    protected void processEmbedded(MetadataAccessor accessor, boolean isEmbeddedId) {
        MetadataDescriptor md = accessor.getMetadataDescriptor();
        
        // Tell the Embeddable class to process itself
        MetadataDescriptor referenceDmd = processEmbeddableClass(accessor, isEmbeddedId);
        
        // Store this descriptor metadata. It may be needed again later on to
        // look up a mappedBy attribute.
        md.addAggregateDmd(referenceDmd);
        
        if (md.hasMappingForAccessor(accessor)) {
            // XML/Annotation merging. XML wins, ignore annotations.
            getLogger().logWarningMessage(MetadataLogger.IGNORE_MAPPING, md, accessor);
        } else {
            // Create an aggregate mapping and do the rest of the work.
            AggregateObjectMapping mapping = new AggregateObjectMapping();
            mapping.setIsReadOnly(false);
            mapping.setIsNullAllowed(true);
            mapping.setReferenceClassName(accessor.getReferenceClassName());
            mapping.setAttributeName(accessor.getAttributeName());    
        
            // Will check for PROPERTY access
            setAccessorMethods(accessor, mapping);
        
            // Handle attribute overrides.
            processAttributeOverrides(accessor, mapping);
            
            // Handles association overrides.
            processAssociationOverrides(accessor, mapping);
        
            // Add the mapping to the descriptor and we are done.
            md.addMapping(mapping);
        }
    }
    
    /**
     * INTERNAL:
     * Process an EmbeddedId or embedded-id element. After processing the 
     * Embeddable class we must add the primary key field names to our 
     * processing entity.
     */
    protected void processEmbeddedId(MetadataAccessor accessor) {
        MetadataDescriptor md = accessor.getMetadataDescriptor();
        
        if (md.ignoreIDAnnotations()) {
            // XML/Annotation merging. XML wins, ignore annotations.
            getLogger().logWarningMessage(MetadataLogger.IGNORE_EMBEDDED_ID, accessor);
        } else {
            // Check if we already processed an EmbeddedId for this entity.
            if (md.hasEmbeddedIdAttribute()) {
                getValidator().throwMultipleEmbeddedIdsFound(md.getJavaClass(), accessor.getAttributeName(), md.getEmbeddedIdAttributeName());
            } 
        
            // WIP - need to clean up this validation exception. Should check
            // that we are not using an IdClass separately that way we can
            // generate a clearer exception.
            
            // Check if we already processed an Id or IdClass.
            if (md.hasPrimaryKeyFields()) {
                getValidator().throwEmbeddedIdAndIdFound(md.getJavaClass(), accessor.getAttributeName(), md.getIdAttributeName());
            }
            
            // Set the PK class.
            md.setPKClass(accessor.getReferenceClass());
            
            // Store the embeddedId attribute name.
            md.setEmbeddedIdAttributeName(accessor.getAttributeName());
        }
            
        // Process the Embedded mapping portion.
        processEmbedded(accessor, true);
    }

    /**
	 * INTERNAL:
	 * Process an entity. This class will fast track any root inheritance
     * processing, be it specified or defaulted.
	 */
	protected void processEntity(String alias, MetadataDescriptor descriptor) {
        // Don't override existing alias.
        if (descriptor.getAlias().equals("")) {
            if (alias.equals("")) {
                alias = Helper.getShortClassName(descriptor.getJavaClassName());
                getLogger().logConfigMessage(MetadataLogger.ALIAS, descriptor, alias);
            }

            // Verify that entity name is not a duplicate
            ClassDescriptor d = m_session.getProject().getDescriptorForAlias(alias);
            if (d != null) {
                getValidator().throwNonUniqueEntityName(d.getJavaClassName(), descriptor.getJavaClassName(), alias);
            }

            descriptor.setAlias(alias);            
            // Set our descriptor on the project
            m_session.getProject().addAlias(alias, descriptor.getDescriptor());
        }
		
		// If we are an inheritance subclass, ensure our root is processed and
        // store its metadata descriptor.
		if (descriptor.isInheritanceSubclass(m_metadataDescriptors)) {
			Class rootClass = descriptor.getInheritanceRootClass();            
            MetadataDescriptor rootDescriptor = getMetadataDescriptor(rootClass);
            
            // Set the flag to ensure we do any inheritance defaulting.
            rootDescriptor.setIsInheritanceRoot(true);
            
            // Process the root descriptor if it hasn't already been done.
            if (! rootDescriptor.isProcessed()) {
                processEntityClass(rootClass);
            }
                
            // Process the inheritance root information if we haven't already.
            if (! rootDescriptor.hasInheritance()) {
                processInheritanceRoot(rootDescriptor);    
            }
            
			// Set our root inheritance descriptor metadata.
			descriptor.setInheritanceRootDmd(rootDescriptor);
			m_relatedEntities.add(descriptor);
		}
	}
	
	/**
	 * INTERNAL:
	 */
	protected abstract void processEntityClass(Class cls);
    
    /**
     * INTERNAL:
     * Process an @Enumerated or enumerated sub-element. The method may still 
     * be called if no @Enumerated or enumerated sub-element has been specified 
     * but the accessor's reference class is a valid enumerated type.
     */
    protected void processEnumerated(MetadataAccessor accessor, boolean isOrdinal, DirectToFieldMapping mapping) {
        Class referenceClass = accessor.getReferenceClass();
        
        // If we have an @Enumerated annotation get the enumerated type.
        if (accessor.hasEnumerated()) {
            if (!MetadataHelper.isValidEnumeratedType(referenceClass)) {
                getValidator().throwInvalidTypeForEnumeratedAttribute(accessor.getJavaClass(), mapping.getAttributeName(), referenceClass);
            }
        }
        
        // Set a EnumTypeConverter on the mapping.
        mapping.setConverter(new EnumTypeConverter(mapping, accessor.getReferenceClassName(), isOrdinal));
    }

    /**
	 * INTENAL:
	 */
    protected abstract void processEnumerated(MetadataAccessor accessor, DirectToFieldMapping mapping);
    
    /**
     * INTERNAL:
     * Process an @Id or id element if there is one.
     */
    protected abstract void processId(MetadataAccessor accessor, DatabaseField field);
    
    /**
     * INTERNAL:
     * Process an @Id or id element if there is one.
     */
    protected void processId(MetadataAccessor accessor, DatabaseField field, Object generatedValue) {
        MetadataDescriptor md = accessor.getMetadataDescriptor();
        
    	if (md.ignoreIDAnnotations()) {
            // Project XML merging. XML wins, ignore annotations/orm xml.
            getLogger().logWarningMessage(getLogger().IGNORE_PRIMARY_KEY, accessor);
        } else {
            String attributeName = accessor.getAttributeName();
            
            if (md.hasEmbeddedIdAttribute()) {
                // We found both an Id and an EmbeddedId, throw an exception.
                getValidator().throwEmbeddedIdAndIdFound(md.getJavaClass(), md.getEmbeddedIdAttributeName(), attributeName);
            }
            
            // If this entity has a pk class, we need to validate our ids. 
            md.validatePKClassId(attributeName, accessor.getReferenceClass());
        
            // Store the Id attribute name. Used with validation and OrderBy.
            md.addIdAttributeName(attributeName);

            // Add the primary key field to the descriptor.            
            md.addPrimaryKeyField(field);
	
            // Pre process the Id for the sequencing.
            preProcessGeneratedValue(generatedValue, accessor, field, md);
        }
    }

    /**
     * INTERNAL:
     * Process an @IdClass or id-class element.  It is used to specify composite 
     * primary keys. The primary keys will be processed and stored from the PK 
     * class so that they may be validated against the fields or properties of 
     * the entity bean. The access type of a primary key class is determined by 
     * the access type of the entity for which it is the primary key.
     */
    protected void processIdClass(Class pkClass, MetadataDescriptor metadataDescriptor) {
        if (pkClass != null) {
            metadataDescriptor.setPKClass(pkClass);
                
            if (metadataDescriptor.ignoreIDAnnotations()) {
                // Project XML merging. XML wins, ignore annotations/orm xml.
                getLogger().logWarningMessage(MetadataLogger.IGNORE_ID_CLASS, metadataDescriptor, pkClass);
            } else {
                if (metadataDescriptor.usesPropertyAccess()) {
                    Method[] methods = MetadataHelper.getDeclaredMethods(pkClass);
                    
                    for (int i = 0; i < methods.length; i++) {
                        Method method = methods[i];
                        String methodName = method.getName();
                
                        if (MetadataHelper.isValidPersistenceMethodName(methodName)) {
                            metadataDescriptor.addPKClassId(MetadataHelper.getAttributeNameFromMethodName(methodName), MetadataHelper.getReturnType(method));
                        }
                    }
                } else {
                    Field[] fields = MetadataHelper.getFields(pkClass);
                    
                    for (int i = 0; i < fields.length; i++) {
                        Field field = fields[i];
                        metadataDescriptor.addPKClassId(field.getName(), MetadataHelper.getType(field));
                    }
                }   
            }
        }
    }
    
    /**
     * INTERNAL:
     * Process an inheritance root class.
     */
    protected abstract void processInheritanceRoot(MetadataDescriptor md);
    
    /**
     * INTERNAL:
     * Process an inheritance subclass.
     */
    protected void processInheritanceSubclass(MetadataDescriptor md) {
        if (md.ignoreInheritanceAnnotations()) {
            // Project XML merging. XML wins, ignore annotations/orm xml
            getLogger().logWarningMessage(MetadataLogger.IGNORE_INHERITANCE, md);
        } else {
            // Log a warning if we are ignoring an entity that has an 
            // inheritance tag.
            if (md.hasInheritanceTag(md.getJavaClass())) {
                getLogger().logWarningMessage(MetadataLogger.IGNORE_INHERITANCE, md);
            }
            
            // Set the parent class on the source descriptor.
            md.setParentClass();
                
            // Get the inheritance root descriptor metadata.
            MetadataDescriptor rootDmd = md.getInheritanceRootDmd();
                
            // Inheritance.stategy() = SINGLE_TABLE, set the flag.
            if (rootDmd.usesSingleTableInheritanceStrategy()) {
                md.setSingleTableInheritanceStrategy();
            } else {
                // Inheritance.stategy() = JOINED, then we need to look for 
                // primary key join column(s)
                List joinColumns = processPrimaryKeyJoinColumns(rootDmd.getPrimaryTableName(), md.getPrimaryTableName(), md.getJavaClass(), md);                        
                for (Iterator iterator = joinColumns.iterator(); iterator.hasNext();) {
                	MetadataJoinColumn mjc = (MetadataJoinColumn) iterator.next();
                	addMultipleTableKeyField(mjc, getLogger().INHERITANCE_PK_COLUMN, getLogger().INHERITANCE_FK_COLUMN, md);
                }
            }    
            
            // Process the discriminator value.
            processDiscriminatorValue(md);
        }
    }
    
    /**
     * INTERNAL:
     * Process join column details from an @JoinColumn or join-column element 
     * into a MetadataJoinColumn and return it.
	 */
    protected MetadataJoinColumn processJoinColumn(MetadataAccessor accessor, String targetTable, MetadataDescriptor sourceMd, String fkFieldName, String pkFieldName, String columnDefinition, String table, boolean isUnique, boolean isNullable, boolean isInsertable, boolean isUpdatable) {
        if (sourceMd.hasCompositePrimaryKey() && (pkFieldName.equals("") || fkFieldName.equals(""))) {
            getValidator().throwIncompleteJoinColumnsSpecified(accessor.getJavaClass(), accessor.getAnnotatedElement());
        }

         // Create a new JoinColumnsDetails to return.
        MetadataJoinColumn mjc = new MetadataJoinColumn();
        
        DatabaseField pkField = new DatabaseField();
        pkField.setName(pkFieldName);
        pkField.setTableName(sourceMd.getPrimaryTableName());
        mjc.setPrimaryKeyField(pkField);
    
        DatabaseField fkField = new DatabaseField();
        fkField.setName(fkFieldName);
        mjc.setForeignKeyField(fkField);
        
        fkField.setUnique(isUnique);
        fkField.setNullable(isNullable);
        fkField.setInsertable(isInsertable);
        fkField.setUpdatable(isUpdatable);
        fkField.setColumnDefinition(columnDefinition);

        if (table.equals("")) {
            // Set the table name if it is not null. If we are processing
            // an embeddable class, then we will not have a table name and 
            // setting a null table name is a bad thing.
            if (targetTable != null) {
                fkField.setTableName(targetTable);
            }
        } else {
            fkField.setTableName(table);
        }
        
        // Flag the metadata descriptor if a default table is being used.
        sourceMd.handlePotentialDefaultTableUsage(pkField);
        accessor.getMetadataDescriptor().handlePotentialDefaultTableUsage(fkField);

        return mjc;
    }
    
    /**
     * INTERNAL:
     */
    protected abstract List processJoinColumns(MetadataAccessor accessor);

    /**
     * INTERNAL:
     * Process a @JoinTable.
     */
    protected abstract void processJoinTable(MetadataAccessor accessor, ManyToManyMapping mapping);
    
    /**
     * INTERNAL:
     * Process a @JoinTable.
     */
    protected void processJoinTable(MetadataAccessor accessor, ManyToManyMapping mapping, List sourceKeys, List targetKeys) {
        // Set up our default field names.
        String defaultSourceFieldName;
        Class sourceClass = accessor.getJavaClass();
        if (accessor.getReferenceMetadataDescriptor().hasManyToManyAccessorFor(sourceClass)) {
            defaultSourceFieldName = accessor.getReferenceMetadataDescriptor().getManyToManyAccessor(sourceClass).getAttributeName();    
        } else {
            defaultSourceFieldName = Helper.getShortClassName(sourceClass.getName());
        }
        
        String defaultTargetFieldName = accessor.getAttributeName();
        
        // Add all the joinColumns (source foreign keys) to the mapping.
        addManyToManyRelationKeyFields(sourceKeys, mapping, accessor, defaultSourceFieldName, accessor.getMetadataDescriptor(), true);
        
        // Add all the inverseJoinColumns (target foreign keys) to the mapping.
        addManyToManyRelationKeyFields(targetKeys, mapping, accessor, defaultTargetFieldName, accessor.getReferenceMetadataDescriptor(), false);
    }
    
    /**
     * INTERNAL:
     * Process a @Lob or lob sub-element. The lob must be specified to process 
     * and create a lob type mapping.
     */
    protected void processLob(MetadataAccessor accessor, DirectToFieldMapping mapping) {
        Class referenceClass = accessor.getReferenceClass();
        
        // Set a TypeConversionConverter on the mapping.
        mapping.setConverter(new TypeConversionConverter(mapping));
		
        // Set the field classification type on the mapping based on the
        // referenceClass type.
        if (MetadataHelper.isValidClobType(referenceClass)) {
            mapping.setFieldClassification(java.sql.Clob.class);    
        } else if (MetadataHelper.isValidBlobType(referenceClass)) {
            mapping.setFieldClassification(java.sql.Blob.class);
        } else {
            // The referenceClass is neither a valide BLOB or CLOB attribute.   
            getValidator().throwInvalidTypeForLOBAttribute(accessor.getJavaClass(), mapping.getAttributeName(), referenceClass);
        }
    }

    /**
     * INTERNAL:
     * WIP - I'd like to see these methods implemented and not have to be
     * abstract ... should be able to extract info from the accessor.
     */
    protected abstract void processManyToMany(MetadataAccessor accessor);

    /**
     * INTERNAL:
     */
    protected void processManyToMany(MetadataAccessor accessor, Class targetEntity, Object[] cascadeTypes, boolean usesIndirection, String mappedBy) {
        MetadataDescriptor md = accessor.getMetadataDescriptor();
		
        // Create and initialize our mapping.
        ManyToManyMapping mapping = new ManyToManyMapping();
        mapping.setIsPrivateOwned(false);
        mapping.setAttributeName(accessor.getAttributeName());
		
        // Will check for PROPERTY access.
        setAccessorMethods(accessor, mapping);
        
        // Set the referenced class on the mapping.
        accessor.setReferenceClass(targetEntity, MetadataLogger.MANY_TO_MANY_MAPPING_REFERENCE_CLASS);
        mapping.setReferenceClassName(accessor.getReferenceClassName());
        
        // Process an OrderBy id there is one.
        processOrderBy(accessor, mapping);
        
        // Process a MapKey if there is one.
        String mapKey = processMapKey(accessor, mapping);
        
        // ** Note the reference class needs to be set on the mapping before
        // setting the indirection policy.
        MetadataHelper.setIndirectionPolicy(usesIndirection, mapping, accessor.getRawClass(), mapKey);
        
        // Process cascade
        processCascadeType(accessor, cascadeTypes, mapping);

        if (mappedBy.equals("")) { 
            // Processing the owning side of a M-M that is process a join table.
            processJoinTable(accessor, mapping);
        } else {
            // We are processing the a non-owning side of a M-M. Must set the
            // mapping read-only.
            mapping.setIsReadOnly(true);
            
            // Get the owning mapping from the reference descriptor metadata.
            ManyToManyMapping ownerMapping = (ManyToManyMapping) getOwningMapping(accessor, mappedBy);

            // The ownerMapping may be null if the target-entity is not defined 
            // in XML
            if (ownerMapping == null) {
            	// Indicates that the mapping requires more information before 
                // initialization.
            	mapping.setRequiresCompletion(true);
            	mapping.setMappedBy(mappedBy);
            } else {
                // Handle partial relationship mapping definition in XML if 
                // owningMapping.requiresCompletion then process...
                if (ownerMapping.requiresCompletion()) {
                	MetadataAccessor owningAccessor = accessor.getReferenceMetadataDescriptor().getAccessorFor(ownerMapping.getAttributeName());
                    processJoinTable(owningAccessor, ownerMapping);
                }

                // Set the relation table name from the owner.
	            mapping.setRelationTableName(ownerMapping.getRelationTableName());
	             
	            // Add all the source foreign keys we found on the owner.
	            mapping.setSourceKeyFields(ownerMapping.getTargetKeyFields());
	            mapping.setSourceRelationKeyFields(ownerMapping.getTargetRelationKeyFields());
	            
	            // Add all the target foreign keys we found on the owner.
	            mapping.setTargetKeyFields(ownerMapping.getSourceKeyFields());
	            mapping.setTargetRelationKeyFields(ownerMapping.getSourceRelationKeyFields());
            }
        }

        // Add the mapping to the descriptor.
        md.addMapping(mapping);
    }

    /**
     * INTERNAL:
     * WIP - I'd like to see these methods implemented and not have to be
     * abstract ... should be able to extract info from the accessor.
     */
    protected abstract void processManyToOne(MetadataAccessor accessor);
    
    /**
     * INTERNAL:
     */
    protected void processManyToOne(MetadataAccessor accessor, Class targetEntity, Object[] cascadeTypes, boolean usesIndirection, boolean isOptional) {
        accessor.setReferenceClass(targetEntity, MetadataLogger.MANY_TO_ONE_MAPPING_REFERENCE_CLASS);
        
        // Initialize our mapping now with what we found.
        OneToOneMapping mapping = initOneToOneMapping(accessor, usesIndirection, cascadeTypes, isOptional);

        // Now process the JoinColumns (if there are any) for this mapping.
        processOwningMappingKeys(mapping, accessor);
        
        // Add the mapping to the descriptor.
        accessor.getMetadataDescriptor().addMapping(mapping);
    }
    
    /**
     * INTERNAL:
     */
    protected abstract String processMapKey(MetadataAccessor accessor, CollectionMapping mapping);

    /**
     * INTERNAL:
     * Process a MapKey for a 1-M or M-M mapping. Will return the map key
     * method name that should be use, null otherwise.
     */
    protected String processMapKey(MetadataAccessor accessor, CollectionMapping mapping, String name) {
        MetadataDescriptor referenceDmd = accessor.getReferenceMetadataDescriptor();
        String mapKeyName = null;
        
        if (name.equals("") && referenceDmd.hasCompositePrimaryKey()) {
            // No persistent property or field name has been provided, and
            // the reference class has a composite primary key class. Let
            // it fall through to return null for the map key. Internally,
            // TopLink will use an instance of the composite primary key
            // class as the map key.
        } else {
            // A persistent property or field name may have have been 
            // provided. If one has not we will default to the primary
            // key of the reference class. The primary key cannot be 
            // composite at this point.
            String fieldOrPropertyName = getName(name, referenceDmd.getIdAttributeName(), getLogger().MAP_KEY_ATTRIBUTE_NAME, accessor);
    
            // Look up the referenceAccessor
            MetadataAccessor referenceAccessor = referenceDmd.getAccessorFor(fieldOrPropertyName);
        
            if (referenceAccessor == null) {
                getValidator().throwCouldNotFindMapKey(fieldOrPropertyName, referenceDmd.getJavaClass(), mapping);
            }
        
            mapKeyName = referenceAccessor.getName();
        }
        
        return mapKeyName;
    }
    
    /**
     * INTERNAL:
     * WIP - I'd like to see these methods implemented and not have to be
     * abstract ... should be able to extract info from the accessor.
     */
    protected abstract void processOneToMany(MetadataAccessor accessor);

    /**
     * INTERNAL:
     * Process an @OneToMany or one-to-many element into a TopLink OneToMany 
     * mapping. If a JoinTable is found however, we must create a ManyToMany 
     * mapping.
     */
    protected void processOneToMany(MetadataAccessor accessor, Object element, String mappedBy) {
        MetadataDescriptor md = accessor.getMetadataDescriptor();
        
        // Should be treated as a uni-directional mapping using a join table.
        // Element can only be null when processing annotations.
        if (element == null || mappedBy.equals("")) {
            // If we find a JoinColumn(s), then throw an exception.
            if (accessor.hasJoinColumn() || accessor.hasJoinColumns()) {
                getValidator().throwUniDirectionalOneToManyHasJoinColumnSpecified(md.getJavaClass(), accessor.getAttributeName());
            }
            
            // Create a M-M mapping and populate it from the OneToMany.
            ManyToManyMapping mapping = new ManyToManyMapping();
            populateCollectionMapping(mapping, element, accessor);
            
            // Process the @JoinTable.
            processJoinTable(accessor, mapping);
            
            // Add the mapping to the descriptor.
            md.addMapping(mapping);
        } else {
            // Create a 1-M mapping and populate it from the @OneToMany.
            OneToManyMapping mapping = new OneToManyMapping();
            populateCollectionMapping(mapping, element, accessor);
            
            // Non-owning side, process the foreign keys from the owner.
            OneToOneMapping ownerMapping = (OneToOneMapping) getOwningMapping(accessor, mappedBy);
            
            // ownerMapping may be null if the target-entity is not defined in XML
            if (ownerMapping == null) {
            	// indicate that the mapping requires more information before initialization 
            	mapping.setRequiresCompletion(true);
            	mapping.setMappedBy(mappedBy);
            } else {
                // handle partial relationship mapping definition in XML
                // if owningMapping.requiresCompletion then process...
                if (ownerMapping.requiresCompletion()) {
                	MetadataAccessor owningAccessor = accessor.getReferenceMetadataDescriptor().getAccessorFor(ownerMapping.getAttributeName());
                	processOwningMappingKeys(ownerMapping, owningAccessor);
                }
                
                Map keys = ownerMapping.getSourceToTargetKeyFields();
                for (Iterator iterator = keys.keySet().iterator(); iterator.hasNext();) {
                	DatabaseField fkField = (DatabaseField) iterator.next();
                    mapping.addTargetForeignKeyField(fkField, (DatabaseField) keys.get(fkField));
                }   
            }
            
            // Add the mapping to the descriptor.
            md.addMapping(mapping);
        }
    }

    /**
     * INTERNAL:
     * WIP - I'd like to see these methods implemented and not have to be
     * abstract ... should be able to extract info from the accessor.
     */
    protected abstract void processOneToOne(MetadataAccessor accessor);

    /**
     * INTERNAL:
     * Process a @OneToOne or one-to-one element into a TopLink 
     * OneToOne mapping.
     */
    protected void processOneToOne(MetadataAccessor accessor, Class targetEntity, Object[] cascadeTypes, boolean usesIndirection, boolean optional, String mappedBy) {
        // Figure out the referenceClass (from the targetEntity) and set it on 
        // the mapping.
        accessor.setReferenceClass(targetEntity, MetadataLogger.ONE_TO_ONE_MAPPING_REFERENCE_CLASS);
        
        // Initialize our mapping now with what we found.
        OneToOneMapping mapping = initOneToOneMapping(accessor, usesIndirection, cascadeTypes, optional);

        if (mappedBy.equals("")) {
            // Owning side, need to look for JoinColumns.
            processOwningMappingKeys(mapping, accessor);
        } else {    
            // Non-owning side, process the foreign keys from the owner.
            OneToOneMapping ownerMapping = ((OneToOneMapping) getOwningMapping(accessor, mappedBy));

            // ownerMapping may be null if the target-entity is not defined in XML
            if (ownerMapping == null) {
            	// indicate that the mapping requires more information before initialization 
            	mapping.setRequiresCompletion(true);
            	mapping.setMappedBy(mappedBy);
            } else { 
                // handle partial relationship mapping definition in XML
                // if owningMapping.requiresCompletion then process...
                if (ownerMapping.requiresCompletion()) {
                	MetadataAccessor owningAccessor = accessor.getReferenceMetadataDescriptor().getAccessorFor(ownerMapping.getAttributeName());
                	processOwningMappingKeys(ownerMapping, owningAccessor);
                }
                
            	mapping.setSourceToTargetKeyFields(ownerMapping.getTargetToSourceKeyFields());
            	mapping.setTargetToSourceKeyFields(ownerMapping.getSourceToTargetKeyFields());
            }
        }
        
        // Add the mapping to the descriptor.
        accessor.getMetadataDescriptor().addMapping(mapping);
    }
    
    /**
     * INTERNAL:
     */
    protected abstract void processOrderBy(MetadataAccessor accessor, CollectionMapping mapping);

    /**
     * INTERNAL:
     * Process an OrderBy for collection mappings. It specifies the ordering 
     * of the elements of a collection valued association at the point when 
     * the association is retrieved.
     * 
     * The syntax of the value ordering element is an orderby_list, as follows:
     * 
     * orderby_list ::= orderby_item [, orderby_item]*
     * orderby_item ::= property_or_field_name [ASC | DESC]
     * 
     * When ASC or DESC is not specified, ASC is assumed.
     * 
     * If the ordering element is not specified, ordering by the primary key
     * of the associated entity is assumed.
     * 
     * The property or field name must correspond to that of a persistent
     * property or field of the associated class. The properties or fields 
     * used in the ordering must correspond to columns for which comparison
     * operators are supported.
     */
    protected void processOrderBy(MetadataAccessor accessor, CollectionMapping mapping, String orderByValue) {
        MetadataDescriptor referenceDmd = accessor.getReferenceMetadataDescriptor();
        
        if (orderByValue.equals("")) {
            // Default to the primary key field name(s).
            List orderByAttributes = referenceDmd.getIdOrderByAttributeNames();
            
            if (referenceDmd.hasEmbeddedIdAttribute()) {
                String embeddedIdAttributeName = referenceDmd.getEmbeddedIdAttributeName();
                
                for (Iterator iterator = orderByAttributes.iterator(); iterator.hasNext();) {
                	String orderByAttribute = (String) iterator.next();
                    mapping.addAggregateOrderBy(embeddedIdAttributeName, orderByAttribute, false);
                }
            } else {
                for (Iterator iterator = orderByAttributes.iterator(); iterator.hasNext();) {
                	String orderByAttribute = (String) iterator.next();
                    mapping.addOrderBy(orderByAttribute, false);
                }
            }
        } else {
            StringTokenizer commaTokenizer = new StringTokenizer(orderByValue, ",");
            
            while (commaTokenizer.hasMoreTokens()) {
                StringTokenizer spaceTokenizer = new StringTokenizer(commaTokenizer.nextToken());
                String propertyOrFieldName = spaceTokenizer.nextToken();
                MetadataAccessor referenceAccessor = referenceDmd.getAccessorFor(propertyOrFieldName);
                
                if (referenceAccessor == null) {
                    getValidator().throwInvalidOrderByValue(accessor.getJavaClass(), propertyOrFieldName, referenceDmd.getJavaClass(), accessor.getName());
                }

                String attributeName = referenceAccessor.getAttributeName();                    
                String ordering = (spaceTokenizer.hasMoreTokens()) ? spaceTokenizer.nextToken() : MetadataConstants.ASCENDING;

                if (referenceAccessor.isEmbedded()) {
                	for (Iterator iterator = referenceDmd.getOrderByAttributeNames().iterator(); iterator.hasNext(); ) {
                		String orderByAttributeName = (String) iterator.next();
                        mapping.addAggregateOrderBy(attributeName, orderByAttributeName, ordering.equals(MetadataConstants.DESCENDING));        
                    }
                } else {
                    mapping.addOrderBy(attributeName, ordering.equals(MetadataConstants.DESCENDING));    
                }
            }
        }
    }
    
    /**
     * INTERNAL:
     * Process the @JoinColumn(s) for the owning side of a one to one mapping.
     */
    protected void processOwningMappingKeys(OneToOneMapping mapping, MetadataAccessor accessor) {
        // The default pk and pk 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).
        String defaultFKFieldName;
        String defaultPKFieldName;
        List joinColumns;
        MetadataDescriptor md = accessor.getMetadataDescriptor();
        MetadataDescriptor referenceDmd = accessor.getReferenceMetadataDescriptor();
        
        if (accessor.isOneToOnePrimaryKeyRelationship()) {
            // The same name as the primary key field of the referenced entity.
            defaultPKFieldName = referenceDmd.getPrimaryKeyFieldName();
            
            // The same name as the primary key field of the referencing entity.
            defaultFKFieldName = md.getPrimaryKeyFieldName();
            
            // Join columns will come from a @PrimaryKeyJoinColumn(s).
            joinColumns = processPrimaryKeyJoinColumns(referenceDmd.getPrimaryTableName(), md.getPrimaryTableName(), accessor.getAnnotatedElement(), md);
        } else {
            // If the pk field (referencedColumnName) is not specified, it 
            // defaults to the primary key of the referenced table.
            defaultPKFieldName = referenceDmd.getPrimaryKeyFieldName();
        
            // 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; "_"; 
            // the name of the referenced primary key column.
            defaultFKFieldName = accessor.getUpperCaseAttributeName() + "_" + defaultPKFieldName;
            
            // Join columns will come from a @JoinColumn(s).
            joinColumns = processJoinColumns(accessor);
        }

        if (! handlePotentialPartialOneToOneRelationshipMapping(mapping, joinColumns)) {
            // Add the source foreign key fields to the mapping.
            for (Iterator iterator = joinColumns.iterator(); iterator.hasNext();) {
                MetadataJoinColumn mjc = (MetadataJoinColumn) iterator.next();
                DatabaseField pkField = mjc.getPrimaryKeyField();
            
                // The mapping may have been partially completed during XML 
                // processing. If so, set the pk field, either "" indicating to 
                // default, or an actual value.
                if (mapping.requiresCompletion()) {
                    pkField.setName(mapping.getXMLPKNameAttribute());
                }
            
                pkField.setName(getName(pkField, defaultPKFieldName, MetadataLogger.PK_COLUMN, accessor));
                DatabaseField fkField = mjc.getForeignKeyField();
            
                // The mapping may have been partially completed during XML 
                // processing. If so, set the fk field, either "" indicating to 
                // default, or an actual value.
                if (mapping.requiresCompletion()) {
                    fkField.setName(mapping.getXMLFKNameAttribute());
                }
            
                fkField.setName(getName(fkField, defaultFKFieldName, MetadataLogger.FK_COLUMN, accessor));
                // Add a source foreign key to the mapping.
                mapping.addForeignKeyField(fkField, pkField);
            
                // If this is a one to one using a primary key association, or if
                // any of the join columns is marked read-only then set the mapping
                // to be read only.
                if (accessor.isOneToOnePrimaryKeyRelationship() || fkField.isReadOnly()) {
                    mapping.setIsReadOnly(true);
                }
            }
        }
    }    
    
    /**
     * INTERNAL:
	 * Process a primary key join column. Returns a MetadataJoinColumn object.
     */
	protected MetadataJoinColumn processPrimaryKeyJoinColumn(String name, String referencedColumnName, String columnDefinition, String sourceTableName, String targetTableName, MetadataDescriptor md) {        
        // Create a new MetadataJoinColumn to return.
        MetadataJoinColumn mjc = new MetadataJoinColumn();
        
        // name is the foreign key field.
        String fkFieldName = name;
        
        // referencedColumnName is the primary key field.
        String pkFieldName = referencedColumnName;

        // Validate completeness for composite primary keys.        
        if (md.hasCompositePrimaryKey() && (pkFieldName.equals("") || fkFieldName.equals(""))) {
            getValidator().throwIncompletePrimaryKeyJoinColumnsSpecified(md.getJavaClass());
        }
        
        DatabaseField pkField = new DatabaseField();
        pkField.setName(pkFieldName);
        pkField.setTableName(sourceTableName);
        mjc.setPrimaryKeyField(pkField);
    
        DatabaseField fkField = new DatabaseField();
        fkField.setName(name);
        fkField.setTableName(targetTableName);
        mjc.setForeignKeyField(fkField);
        
        fkField.setColumnDefinition(columnDefinition);
        
        // Flag the metadata descriptor if a default table is being used.
        md.handlePotentialDefaultTableUsage(fkField);
        
        return mjc;
	}

    /**
     * INTERNAL;
     */
    protected abstract List processPrimaryKeyJoinColumns(Object[] pkJoinCols, String sourceTableName, String targetTableName, MetadataDescriptor md);
    
	/**
	 * INTERNAL:
	 */
    protected abstract List processPrimaryKeyJoinColumns(String sourceTableName, String targetTableName, Object element, MetadataDescriptor md);
    
    /**
     * INTERNAL:
     * Process a relationship accessor.
     */
    protected void processRelationshipAccessor(MetadataAccessor accessor) {
        // The processing of this accessor may have been fast tracked through a 
        // non-owning relationship. If so, no processing is required.
        if (accessor.needsProcessing()) {
            if (accessor.getMetadataDescriptor().hasMappingForAccessor(accessor)) {
                // XML/Annotation merging. XML wins, ignore annotations.
                getLogger().logWarningMessage(getLogger().IGNORE_MAPPING, accessor);
            } else {
                // If a @Column is specified then throw an exception.
                if (accessor.hasColumn()) {
                    getValidator().throwRelationshipHasColumnSpecified(accessor.getJavaClass(), accessor.getAttributeName());
                }
                
                // Process the correct type of relationship on this accessor. 
                // Look for the non-defaulted relationship types first. That 
                // is, 1-1 and 1-M can default so check them last.
                if (accessor.isManyToOne()) {
                    processManyToOne(accessor);
                }  else if (accessor.isManyToMany()) {
                    processManyToMany(accessor);
                } else if (accessor.isOneToMany()) {
                    processOneToMany(accessor);
                } else if (accessor.isOneToOne()) {
                    processOneToOne(accessor);
                }
            }
            
            // Set its processing completed flag to avoid double processing.
            accessor.setNeedsProcessing(false);
        }
    }
    
    /**
     * INTERNAL:
     * Process an @SecondaryTable or secondary-table element and add it to 
     * descriptor. Method assumes that the class has been processed for a 
     * primary table and primary key.
     */
    protected void processSecondaryTable(String name, String catalog, String schema, Object[] uniqueConstraints, Object[] pkJoinCols, MetadataDescriptor md) {
        // Build a database table
        DatabaseTable table = buildTable(name, catalog, schema, md);
        
        // Process the UniqueConstraints for this table.
        processUniqueConstraints(uniqueConstraints, table);

        // Add the table to the descriptor.        
        md.addTable(table);
        
        // Now process the PrimaryKeyJoinColumns.
        String primaryTableName = md.getPrimaryTableName();
        String secondaryTableName = table.getQualifiedName();
        List metadataJoinColumns = processPrimaryKeyJoinColumns(pkJoinCols, primaryTableName, secondaryTableName, md);
        
        // Validate the primary key join columns we found.
        if (md.hasCompositePrimaryKey()) {
            // Composite primary key, validate all join columns are specified.
            if (metadataJoinColumns.size() != md.getPrimaryKeyFields().size()) {
                getValidator().throwIncompletePrimaryKeyJoinColumnsSpecified(md.getJavaClass());
            } 
        } else {
            // Single primary key relationship, add a defaulting join column 
            // if needed.
            addJoinColumnDefault(metadataJoinColumns, primaryTableName, secondaryTableName, md);
        }
        
        // Add the multiple table key fields to the mapping.
        for (Iterator iterator = metadataJoinColumns.iterator(); iterator.hasNext();) {
        	MetadataJoinColumn mjc = (MetadataJoinColumn) iterator.next();
            addMultipleTableKeyField(mjc, MetadataLogger.SECONDARY_TABLE_PK_COLUMN, MetadataLogger.SECONDARY_TABLE_FK_COLUMN, md);         
        }
    }

    /**
     * INTERNAL:
     * Process a potential serializable attribute. If the class implements 
     * the Serializable interface then set a SerializedObjectConverter on 
     * the mapping.
     */
    protected void processSerialized(MetadataAccessor accessor, DirectToFieldMapping mapping) {
        Class referenceClass = accessor.getReferenceClass();
        
        if (Helper.classImplementsInterface(referenceClass, Serializable.class)) {
            mapping.setConverter(new SerializedObjectConverter(mapping));
        } else {
            getValidator().throwInvalidTypeForSerializedAttribute(accessor.getJavaClass(), mapping.getAttributeName(), referenceClass);
        }
    }
    
	/**
	 * INTERNAL:
	 * Process a database table from the candidate table, catalog and schema
     * names. The table is set as the primary table for descriptor. 
	 */
	protected void processTable(String name, String catalog, String schema, Object[] uniqueConstraints, MetadataDescriptor md) {
        DatabaseTable dbTable = buildTable(name, catalog, schema, md);
        
        if (uniqueConstraints != null) {
            processUniqueConstraints(uniqueConstraints, dbTable);
        }
        
        md.setPrimaryTable(dbTable);
	}

	/**
	 * INTERNAL:
	 */
    protected abstract void processTemporal(MetadataAccessor accessor, DirectToFieldMapping mapping);

    /**
     * INTERNAL:
     * Process a temporal type. The method may still be called if no @Temporal 
     * or temporal sub-element has been specified but the accessor's reference 
     * class is a valid temporal type. 
     */
    protected void processTemporal(MetadataAccessor accessor, String temporalType, DirectToFieldMapping mapping) {
        Class entityClass = accessor.getJavaClass();
        Class referenceClass = accessor.getReferenceClass();
        String attributeName = accessor.getAttributeName();

        if (MetadataHelper.isValidTemporalType(referenceClass)) {
            // Set a TypeConversionConverter on the mapping.
            mapping.setConverter(new TypeConversionConverter(mapping));
            mapping.setFieldClassification(MetadataHelper.getFieldClassification(temporalType));
        } else {
            getValidator().throwInvalidTypeForTemporalAttribute(entityClass, attributeName, referenceClass);
        }
    }
    
    /**
     * INTERNAL:
     */
    protected abstract void processUniqueConstraints(Object[] uniqueConstraints, DatabaseTable dbTable);
    
    /**
     * INTERNAL:
     * Set the getter and setter access methods on a mapping.
     */
    protected void setAccessorMethods(MetadataAccessor accessor, DatabaseMapping mapping) {
        MetadataDescriptor md = accessor.getMetadataDescriptor();
        
        if (md.usesPropertyAccess()) {
            String getMethodName = accessor.getName();
            String setMethodName = md.getSetMethodName(getMethodName);
            mapping.setGetMethodName(getMethodName);
            mapping.setSetMethodName(setMethodName);
        }
    }
    
    /**
     * INTERNAL:
     * Set the cascade type on a mapping.
     */
    protected void setCascadeType(String type, ForeignReferenceMapping mapping) {
        if (type.equals(MetadataConstants.CASCADE_ALL) || type.equals(MetadataConstants.ALL)) {
            mapping.setCascadeAll(true);
        } else if (type.equals(MetadataConstants.CASCADE_MERGE) || type.equals(MetadataConstants.MERGE)) {
            mapping.setCascadeMerge(true);
        } else if (type.equals(MetadataConstants.CASCADE_PERSIST) || type.equals(MetadataConstants.PERSIST)) {
            mapping.setCascadePersist(true);
        }  else if (type.equals(MetadataConstants.CASCADE_REFRESH) || type.equals(MetadataConstants.REFRESH)) {
            mapping.setCascadeRefresh(true);
        } else if (type.equals(MetadataConstants.CASCADE_REMOVE) || type.equals(MetadataConstants.REMOVE)) {
            mapping.setCascadeRemove(true);
        }
    }
    
    /** 
     * INTERNAL:
	 * Use this method to set the correct class loader that should be used
     * during processing.
	 */
	public void setClassLoader(ClassLoader loader) {
        m_loader = loader;
    }

	/**
     * INTERNAL:
     * Apply the table and field names to the DatabaseField as required. 
     */
    protected void setColumnDetails(DatabaseField dbField, String tableName, String fieldName, String attributeName, Object element) {
        // Set the table name if it is not null. If we are processing an 
        // embeddable class, then we will not have a table name and setting
        // a null table name is a bad thing.
        if (tableName != null) {
            dbField.setTableName(tableName);
        }
        
        // Field name may still be null at this point, may have to default.
        dbField.setName(getName(fieldName, attributeName.toUpperCase(), MetadataLogger.COLUMN, element));
    }

    /**
     * INTERNAL: 
     * This method should be called after the classLoader has changed. In 
     * m_metadataDescriptors it substitutes "old" classes for "new" classes.
     * Assumes that descriptors already have the correct classes.
     */
     public void updateClassesInMetadata() {
        if (m_metadataDescriptors == null) {
            return;
        }
        
        HashMap newMetadataDescriptors = new HashMap(m_metadataDescriptors.size());
        Iterator iterator = m_metadataDescriptors.values().iterator();
        
        while (iterator.hasNext()) {
            MetadataDescriptor md = (MetadataDescriptor) iterator.next();
            Class newClass = md.getDescriptor().getJavaClass();
            md.setJavaClass(newClass);
            md.setInheritanceRootClass(null); // reset lazily
            newMetadataDescriptors.put(newClass, md);
        }
        
        m_metadataDescriptors = newMetadataDescriptors;
    }
}
