/*
 * 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]
 */
// Copyright (c) 1998, 2006, Oracle. All rights reserved.  
package oracle.toplink.essentials.internal.ejb.cmp3.annotations;

import java.util.Map;
import java.util.List;
import java.util.HashSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.Collection;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.AnnotatedElement;

import javax.persistence.EnumType;
import javax.persistence.FetchType;
import javax.persistence.CascadeType;

import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Basic;
import javax.persistence.MapKey;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.OrderBy;
import javax.persistence.IdClass;
import javax.persistence.Temporal;
import javax.persistence.PostLoad;
import javax.persistence.OneToOne;
import javax.persistence.QueryHint;
import javax.persistence.Transient;
import javax.persistence.ManyToOne;
import javax.persistence.PreUpdate;
import javax.persistence.OneToMany;
import javax.persistence.JoinTable;
import javax.persistence.PreRemove;
import javax.persistence.PostRemove;
import javax.persistence.PrePersist;
import javax.persistence.JoinColumn;
import javax.persistence.PostUpdate;
import javax.persistence.NamedQuery;
import javax.persistence.ManyToMany;
import javax.persistence.Enumerated;
import javax.persistence.PostPersist;
import javax.persistence.Inheritance;
import javax.persistence.JoinColumns;
import javax.persistence.FieldResult;
import javax.persistence.NamedQueries;
import javax.persistence.ColumnResult;
import javax.persistence.EntityResult;
import javax.persistence.GeneratedValue;
import javax.persistence.TableGenerator;
import javax.persistence.SecondaryTable;
import javax.persistence.SecondaryTables;
import javax.persistence.EntityListeners;
import javax.persistence.UniqueConstraint;
import javax.persistence.NamedNativeQuery;
import javax.persistence.AttributeOverride;
import javax.persistence.SequenceGenerator;
import javax.persistence.AttributeOverrides;
import javax.persistence.NamedNativeQueries;
import javax.persistence.DiscriminatorValue;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.AssociationOverride;
import javax.persistence.AssociationOverrides;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.SqlResultSetMappings;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.PrimaryKeyJoinColumns;
import javax.persistence.ExcludeDefaultListeners;
import javax.persistence.ExcludeSuperclassListeners;

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

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

import oracle.toplink.essentials.internal.ejb.cmp3.EJBQueryImpl;
import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataHelper;
import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataAccessor;
import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataConstants;
import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataProcessor;
import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataJoinColumn;
import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataDescriptor;
import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataEntityListener;

import oracle.toplink.essentials.mappings.DatabaseMapping;
import oracle.toplink.essentials.mappings.OneToOneMapping;
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.queryframework.EJBQLPlaceHolderQuery;

/**
 * Process the CMP annotations on a set of classes into a given TopLink 
 * session's project.
 * 
 * @author Guy Pelletier
 * @since TopLink 10.1.3/EJB 3.0 Preview
 */
public class EJBAnnotationsProcessor extends MetadataProcessor {
    private Collection<Class> m_classes;

    // Store the named queries when processing O/R annotations.
    private HashSet<AnnotationsDescriptor> m_entitiesWithQueries;		
    
    // Responsible for all sequencing-related processing.
    private AnnotationsSequencingProcessor m_sequencingProcessor;

    /**
     * INTERNAL:
     */
    public EJBAnnotationsProcessor(AbstractSession session, ClassLoader loader, Collection<Class> classes, boolean enableLazyForOneToOne) {
        m_loader = loader;
        m_classes = classes;
        m_session = session;
        m_metadataDescriptors = new HashMap();
        m_validator = new AnnotationsValidator();
        m_logger = new AnnotationsLogger(m_session);
        m_enableLazyForOneToOne = enableLazyForOneToOne;
        m_sequencingProcessor = new AnnotationsSequencingProcessor();
        m_relatedEntities = new HashSet<AnnotationsDescriptor>();
        m_entitiesWithQueries = new HashSet<AnnotationsDescriptor>();
    }
    
    /**
     * INTERNAL:
     * Called for XML/Annotation merging.
     * WIP - entities list no longer needs to be passed in here.
     */
    public EJBAnnotationsProcessor(AbstractSession session, ClassLoader loader, Collection<Class> entities, boolean enableLazyForOneToOne, HashMap<Class, MetadataDescriptor> metadataDescriptors) {
        this(session, loader, metadataDescriptors.keySet(), enableLazyForOneToOne);
        
        // Convert the metadata descriptors (if there are any) to annotations 
        // meta data from xml meta data.
        if (metadataDescriptors != null && !metadataDescriptors.isEmpty()) {
            for (MetadataDescriptor mdd : metadataDescriptors.values()) {
                AnnotationsDescriptor descriptor = new AnnotationsDescriptor(mdd);
                m_metadataDescriptors.put(descriptor.getJavaClass(), descriptor);
            }
        }
    }	
    
    /**
     * INTERNAL: 
     * Method to place EntityListener's on the descriptors from the given 
     * session. This call is made from the EntityManagerSetup deploy call.
     */
    public void addEntityListeners(AbstractSession session, Collection<Class> entityClasses) {
        updateClassesInMetadata();
         
        for (Class entityClass: entityClasses) {
            AnnotationsDescriptor descriptor = (AnnotationsDescriptor) m_metadataDescriptors.get(entityClass);
            
            // Process the exclude default listener flag.
            processExcludeDefaultListeners(descriptor);
            
            // Process the exclude superclass listener flag.
            processExcludeSuperclassListeners(descriptor);
            
            // Process the default listeners.
            processDefaultListeners(descriptor);

            // Process the entity listeners.
            processEntityListeners(descriptor); 
                
            // Process the entity class for lifecycle callback methods.
            processEntityEventListener(descriptor);
        }
    }
    
    /**
     * INTERNAL:
     * Method to place NamedQueries and NamedNativeQueries on the given session. 
     * This call is made from the EntityManagerSetup deploy call.
     */
    public void addNamedQueriesToSession(AbstractSession session) {
        for (AnnotationsDescriptor descriptor : m_entitiesWithQueries) {
            for (NamedQuery namedQuery : descriptor.getNamedQueries()) {
                processNamedQuery(namedQuery, descriptor, session);
            }
            
            for (NamedNativeQuery namedNativeQuery : descriptor.getNamedNativeQueries()) {
                processNamedNativeQuery(namedNativeQuery, descriptor, session);
            }
        }
    }
        
    /**
     * INTERNAL:
     * In the case of the XML processor using a defaulted primary key field name 
     * during processing, check for the actual primary key field name in 
     * annotations - if one exists, set it as the primary key field for the 
     * related descriptor, and update all applicable fields accordingly.  If
     * one does not exist, throw an exception.
     * 
     * This method assumes that the 0th element in the descriptor's primary
     * key fields list is the one defaulted in XML.
     */
    protected void adjustPrimaryKeyNameOnExistingFields(MetadataDescriptor md) {
    	ArrayList pkFields = (ArrayList) md.getPrimaryKeyFields();
    	// If there is no @Id or id element for this entity, throw an exception.
    	if (pkFields.size() <= 1) {
            getValidator().throwNoPrimaryKeyAnnotationsFound(md.getJavaClass());
    	}
        
    	// WIP - implement support from composite primary keys, that is, no id 
        // element in XML, but composite primary key in annotations.
    	if (pkFields.size() > 2) {
            getValidator().throwUnspecifiedCompositePrimaryKeyNotSupported(md.getJavaClass());
    	}
        
    	// The first primary key processed in annotations is the valid one.
    	DatabaseField validPKField = (DatabaseField) pkFields.get(1);
    	// Remove the defaulted (invlaid) primary key.
    	pkFields.remove(0);
        
        // Now we need to update any references to the default pk field handle 
        // non-complex case, where the field name is the default name.
        for (DatabaseField dbField : (List<DatabaseField>) md.getFieldsWithDefaultPrimaryKeySet()) {
            dbField.setName(validPKField.getName());
        }
        
    	// Handle the complex case, where the field name is composed of an
    	// attribute/entity name, concatenated with an underscore and the
    	// default name, i.e. XMLOrderBean_TOPLINKDEFAULTID
        for (DatabaseField dbField : (List<DatabaseField>) md.getFieldsWithComplexDefaultPrimaryKeySet()) {
    		String defaultFieldName = dbField.getName();
    		String validFieldName = defaultFieldName.substring(0, defaultFieldName.lastIndexOf("_")+1) + validPKField.getName();
    		dbField.setName(validFieldName);
    	}
    }
    
    /**
     * INTERNAL:
     * In the case of the XML processor using a defaulted table name during processing, 
     * check for the actual table name in annotations - if one exists, set it as the 
     * primary database table for the related descriptor, and update all applicable 
     * fields accordingly.
     */
    protected void adjustTableOnExistingFields(MetadataDescriptor md) {
    	// create the primary table based on annotations
    	Table table = AnnotationsHelper.getAnnotation(Table.class, md);
    	if (table == null) {
    		// do nothing - use the default table created by the XML Processor
    		return;
    	}
        
    	// Process the table and add set it as primary.
    	processTable(table.name(), table.catalog(), table.schema(), table.uniqueConstraints(), md);
    	
    	// Update any references to the default table.
        for (DatabaseField dbField : (List<DatabaseField>) md.getFieldsWithDefaultPrimaryTableSet()) {
    		dbField.setTable((DatabaseTable)md.getDescriptor().getTables().elementAt(0));
    	}
    }
    
    /**
     * INTERNAL:
     * Method to return the name of the join table.
     */
     protected DatabaseTable buildJoinTable(JoinTable joinTable, MetadataAccessor accessor) {
    	 DatabaseTable dbJoinTable;
    	 if (joinTable != null) {
    		 dbJoinTable = buildJoinTable(joinTable.name(), joinTable.catalog(), joinTable.schema(), accessor);

    		 // Process the @UniqueConstraints for this table.
    		 processUniqueConstraints(joinTable.uniqueConstraints(), dbJoinTable);
    	 } else {
    		 dbJoinTable = buildJoinTable(accessor);  
    	 }

    	 return dbJoinTable;
    }
    
    /** 
     * INTERNAL:
     * Return the classes set for processing
     */
    public Collection<Class> getClasses() {
        return m_classes;
    }
   
    /** 
     * INTERNAL:
	 * Return the logger used by this processor.
	 */
	public AnnotationsLogger getLogger() {
		return (AnnotationsLogger) m_logger;
    }
    
    /**
     * INTERNAL:
	 * Lazily create a metadata/descriptor for any given class. 
     */
	public AnnotationsDescriptor getMetadataDescriptor(Class cls) {
		AnnotationsDescriptor descriptor = (AnnotationsDescriptor) m_metadataDescriptors.get(cls);
        
		if (descriptor == null) {
            // WIP - in theory all this might be able to go away ...
            ClassDescriptor descriptorOnProject = MetadataHelper.findDescriptor(m_session.getProject(),cls);
            
            // We don't have a descriptor, check if there is a descriptor for 
            // this class on the project already.
            if (descriptorOnProject != null) {
                // Wrap the existing descriptor into a AnnotationsDescriptor.
                descriptor = new AnnotationsDescriptor(descriptorOnProject, cls);
            } else {
                // Create a new AnnotationsDescriptor and add it to the project.
                descriptor = new AnnotationsDescriptor(cls);
                m_session.getProject().addDescriptor(descriptor.getDescriptor());
            }      
            
            m_metadataDescriptors.put(cls, descriptor);
        } 
        
        return descriptor;
	}
    
    /** 
     * INTERNAL:
	 * Return the annotations validator.
	 */
	protected AnnotationsValidator getValidator() {
        return (AnnotationsValidator) m_validator;
    }
    
	/**
	 * INTERNAL:
	 * Satisfy the abstract method declaration in MetadataProcessor
	 */
	protected void handlePotentialDefaultPrimaryKeyUsage(DatabaseField dbField, MetadataAccessor accessor, String defaultName, String context) {
        // Do nothing ...
    }
    
    /**
	 * INTERNAL:
	 * Satisfy the abstract method declaration in MetadataProcessor
	 */
    protected void handlePotentialDefaultPrimaryKeyUsage(DatabaseField dbField, MetadataDescriptor md) {
        // Do nothing ...
    }
    
    /**
     * INTERNAL:
     * Annotations processor does not need to handle this.
     */
    protected boolean handlePotentialPartialOneToOneRelationshipMapping(OneToOneMapping mapping, List joinColumns) {
        return false;
    }
    
    /**
     * INTERNAL:
     * Annotations processor does not need to handle this.
     */
    protected boolean handlePotentialPartialManyToManyRelationshipMapping(ManyToManyMapping mapping, boolean isSource, List relationKeys) {
        return false;
    }

    /**
     * INTERNAL:
     */
    protected boolean isTransient(AnnotatedElement annotatedElement, int modifier, AnnotationsDescriptor descriptor) {
        if (AnnotationsHelper.isAnnotationPresent(Transient.class, annotatedElement, descriptor)) {
            if (AnnotationsHelper.getDeclaredAnnotationsCount(annotatedElement, descriptor) > 1) {
                getValidator().throwMappingAnnotationsAppliedToTransientAttribute(annotatedElement);    
            }
            
            return true;
        } else if (Modifier.isTransient(modifier)) {
            if (AnnotationsHelper.getDeclaredAnnotationsCount(annotatedElement, descriptor) > 0) {
                getValidator().throwMappingAnnotationsAppliedToTransientAttribute(annotatedElement);    
            }
            
            return true;
        }
        
        return false;
    }

    /**
     * INTERNAL:
     * Return true is this annotated element is not marked transient, static or 
     * abstract.
     */
    protected boolean isValidPersistenceElement(AnnotatedElement annotatedElement, int modifiers, AnnotationsDescriptor descriptor) {
        return ! (isTransient(annotatedElement, modifiers, descriptor) || Modifier.isStatic(modifiers) || Modifier.isAbstract(modifiers));
    }
    
    /**
     * INTERNAL:
     * Check to see if this is a valid field to process for persistence. It is 
     * valid if it is not static, transient or has a @Transient specified.
     */
    protected boolean isValidPersistenceField(Field field, AnnotationsDescriptor descriptor) {
        return (isValidPersistenceElement(field, field.getModifiers(), descriptor));
    }
    
    /**
     * INTERNAL:
     * Check to see if this is a valid method to process for persistence. It is 
     * valid if it is not static, transient or has a @Transient specified.
     */
    protected boolean isValidPersistenceMethod(Method method, AnnotationsDescriptor descriptor) {
        // Ignore methods marked transient, static or abstract.
        if (isValidPersistenceElement(method, method.getModifiers(), descriptor)) {
            // Look for methods that begin with "get" or "is", ignore all others.
            String methodName = method.getName();
            if (MetadataHelper.isValidPersistenceMethodName(methodName)) {
                // Ignore get methods with parameters.
                if (method.getParameterTypes().length > 0) {
                    return false;
                }
            
                String setMethodName = MetadataHelper.getSetMethodName(method, descriptor.getJavaClass());
            
                if (setMethodName == null) {
                    if (AnnotationsHelper.getDeclaredAnnotationsCount(method, descriptor) > 0) {
                        // We decorated the property with annotations, but have 
                        // no corresponding setter property.
                        getValidator().throwNoCorrespondingSetterMethodDefined(descriptor.getJavaClass(), method);
                    }
                    
                    getLogger().logWarningMessage(AnnotationsLogger.IGNORE_GET_METHOD, descriptor, method);
                } else {
                    // Store the setMethodName for later retrieval to avoid 
                    // figuring it out twice.
                    descriptor.addSetMethodName(methodName, setMethodName);
                    return true;
                }
            }
        }
        
        return false;
    }
    
    /**
     * INTERNAL:
     * Method to init a collection mapping from a @OneToMany.
     */
    protected void populateCollectionMapping(CollectionMapping mapping, Object oneToManyAnnotation, MetadataAccessor accessor) {
    	OneToMany oneToMany = (OneToMany) oneToManyAnnotation;
        
        // Process the annotation specifics if there is one (may have been
        // defaulted). These are our defaults otherwise.
        boolean usesIndirection = false;
        Class targetEntity = void.class;
        CascadeType[] cascade = {};
		
        if (oneToMany != null) {
            // OneToMany.fetch()
            usesIndirection = oneToMany.fetch() == FetchType.LAZY;
            // OneToMany.targetEntity()
            targetEntity = oneToMany.targetEntity();
            // OneToMany.cascade()
            cascade = oneToMany.cascade();
        }
		
        populateCollectionMapping(mapping, accessor, targetEntity, cascade, usesIndirection);
    }
    
    /**
     * INTERNAL:
     */
    protected void preProcessGeneratedValue(Object generatedValue, MetadataAccessor accessor, DatabaseField field, MetadataDescriptor descriptor) {
    	m_sequencingProcessor.preProcessGeneratedValue((GeneratedValue) generatedValue, (AnnotationsAccessor) accessor, field, (AnnotationsDescriptor) descriptor);
    }
    
    /**
     * INTERNAL:
     */
    protected void preProcessSequencing(MetadataAccessor ma) {
        AnnotationsAccessor accessor = (AnnotationsAccessor) ma;
        preProcessSequencing(accessor.getAnnotatedElement(), accessor.getMetadataDescriptor());
    }
    
    /**
     * INTERNAL:
     * Preprocess the sequencing annotations. Gather the necessary info. The 
     * real sequencing setup is performed after all the sequencing-related 
     * annotations on all classes are preprocessed.
     */
    protected void preProcessSequencing(AnnotatedElement annotatedElement, AnnotationsDescriptor descriptor) {
        TableGenerator tableGenerator = AnnotationsHelper.getAnnotation(TableGenerator.class, annotatedElement, descriptor);
        m_sequencingProcessor.preProcessTableGenerator(tableGenerator, annotatedElement);
        
        SequenceGenerator sequenceGenerator = AnnotationsHelper.getAnnotation(SequenceGenerator.class, annotatedElement, descriptor);
        m_sequencingProcessor.preProcessSequenceGenerator(sequenceGenerator, annotatedElement);
    }
    
    /**
     * INTERNAL:
     * Create mappings from the fields directly.
     */
    protected void processAccessorFields(Class cls, AnnotationsDescriptor descriptor) {
        for (Field field : MetadataHelper.getFields(cls)) {
            if (isValidPersistenceField(field, descriptor)) {
                processAccessor(new AnnotationsAccessor(field, this, descriptor));
            }
        }
    }
    
    /**
     * INTERNAL:
     * Create mappings via the class properties.
     */
    protected void processAccessorMethods(Class cls, AnnotationsDescriptor descriptor) {
        for (Method method : MetadataHelper.getDeclaredMethods(cls)) {
            if (isValidPersistenceMethod(method, descriptor)) {
                processAccessor(new AnnotationsAccessor(method, this, descriptor));
            }
        }
    }
    
    /**
     * INTERNAL:
     * Process the accessors for the given descriptor metadata class.
     */
    protected void processAccessors(MetadataDescriptor descriptor) {
        processAccessors(descriptor.getJavaClass(), descriptor.usesPropertyAccess(), descriptor);
    }
    
    /**
     * INTERNAL:
     * Process the accessors for the given descriptor metadata class.
     */
    protected void processAccessors(Class cls, boolean usePropertyAccess, MetadataDescriptor md) {
        AnnotationsDescriptor descriptor = (AnnotationsDescriptor) md;
        
        // Process the fields or methods on the class.
        if (usePropertyAccess) {
            processAccessorMethods(cls, descriptor);
        } else {
            processAccessorFields(cls, descriptor);
        }
        
        descriptor.setIsProcessed(true);
    }
    
    /**
     * INTERNAL:
     * Process a @JoinTable.
     */
    protected void processJoinTable(MetadataAccessor accessor, ManyToManyMapping mapping) {
        JoinTable joinTable = AnnotationsHelper.getAnnotation(JoinTable.class, accessor);
        
        // Figure out the join table name and set it on the mapping.
        DatabaseTable relationTable = buildJoinTable(joinTable, accessor);
        mapping.setRelationTable(relationTable);
        String relationTableName = relationTable.getQualifiedName();

        // Process the JoinColumns and InverseJoinColumns.
        ArrayList<MetadataJoinColumn> sourceKeys = new ArrayList<MetadataJoinColumn>();
        ArrayList<MetadataJoinColumn> targetKeys = new ArrayList<MetadataJoinColumn>();
        
        // If the join table is not null, look for join columns.
        if (joinTable != null) {
            // Process the JoinColumns - source keys.
            for (JoinColumn joinColumn : joinTable.joinColumns()) {
                sourceKeys.add(processJoinColumn(joinColumn, accessor, relationTableName, accessor.getMetadataDescriptor()));
            }

            // Process the InverseJoinColumns - target keys.
            for (JoinColumn inverseJoinColumn : joinTable.inverseJoinColumns()) {
                targetKeys.add(processJoinColumn(inverseJoinColumn, accessor, relationTableName, accessor.getReferenceMetadataDescriptor()));
            }
        }

        processJoinTable(accessor, mapping, sourceKeys, targetKeys);
    }
    
    /**
     * INTERNAL:
     * Process an @AssociationOverride for an Entity (or MappedSuperclass) 
     * that inherits from a MappedSuperclass.
     * 
     * It will also look for an @AssociationOverride.
     */
    protected void processAssociationOverrides(Class cls, AnnotationsDescriptor descriptor) {
        // Look for an @AssociationOverrides.
        AssociationOverrides associationOverrides = AnnotationsHelper.getAnnotation(AssociationOverrides.class, cls, descriptor);
        if (associationOverrides != null) {
            for (AssociationOverride associationOverride : associationOverrides.value()) {
                descriptor.addAssociationOverride(associationOverride.name(), associationOverride.joinColumns());
            }
        }
        
        // Look for an @AssociationOverride.
        AssociationOverride associationOverride = AnnotationsHelper.getAnnotation(AssociationOverride.class, cls, descriptor);	
        if (associationOverride != null) {
            descriptor.addAssociationOverride(associationOverride.name(), associationOverride.joinColumns());
        }
    }
    
    /**
     * INTERNAL:
     * Process an @AssociationOverrides for an embedded object, that is, an
     * aggregate object mapping in TopLink. 
     * 
     * It will also look for an @AssociationOverride.
     */
    protected void processAssociationOverrides(MetadataAccessor accessor, AggregateObjectMapping mapping) {
        // Look for an @AssociationOverrides.
        AssociationOverrides associationOverrides = AnnotationsHelper.getAnnotation(AssociationOverrides.class, accessor);
        if (associationOverrides != null) {
            for (AssociationOverride associationOverride : associationOverrides.value()) {
                processAssociationOverride(associationOverride, accessor, mapping);
            }
        }
        
        // Look for an @AssociationOverride.
        AssociationOverride associationOverride = AnnotationsHelper.getAnnotation(AssociationOverride.class, accessor);	
        if (associationOverride != null) {
            processAssociationOverride(associationOverride, accessor, mapping);
        }
    }
    
    /**
     * INTERNAL:
     * Process an @AssociationOverride for an embedded object, that is, an 
     * aggregate object mapping in TopLink. 
     * 
     * This functionality is not supported in XML, hence why this method is 
     * defined here instead of on MetadataProcessor.
     * 
     * Also this functionality is currently optional in the EJB 3.0 spec, but
     * since TopLink can handle it, it is implemented and assumes the user has
     * properly configured its use since it will fail silently.
	 */
	protected void processAssociationOverride(AssociationOverride associationOverride, MetadataAccessor accessor, AggregateObjectMapping aggregateMapping) {
        MetadataDescriptor descriptor = accessor.getMetadataDescriptor();
        MetadataDescriptor aggregateDescriptor = accessor.getReferenceMetadataDescriptor();
        
        // AssociationOverride.name(), the name of the attribute we want to
        // override.
        String name = associationOverride.name();
        DatabaseMapping mapping = aggregateDescriptor.getMappingForAttributeName(name);
        
        if (mapping == null) {
            // WIP - For now fail silently.
            //throw ValidationException.invalidEmbeddableAttribute(aggregateDescriptor.getJavaClass(), name, descriptor.getJavaClass(), aggregateMapping.getAttributeName());
        }
        
        if (mapping.isOneToOneMapping()) {
            int index = 0;
            
            for (JoinColumn joinColumn : associationOverride.joinColumns()) {
                // We can't change the mapping from the aggregate descriptor
                // so we have to add field name translations. This needs to be
                // tested since I am not entirely sure if this will acutally
                // work.
                // In composite primary key case, how do we association the
                // foreign keys? Right now we assume the association overrides
                // are specified in the same order as the original joinColumns,
                // therefore in the same order the foreign keys were added to
                // the mapping.
                DatabaseField fkField = (DatabaseField) ((OneToOneMapping) mapping).getForeignKeyFields().elementAt(index++);
                aggregateMapping.addFieldNameTranslation(joinColumn.name(), fkField.getName());
            }   
        } else {
            // WIP - For now fail silently.
        }
	}
    
    /**
     * INTERNAL:
     * Process an @AttributeOverride for an Entity (or MappedSuperclass) 
     * that inherits from a MappedSuperclass. 
     * 
     * It will also look for an @AttributeOverride.
     */
    protected void processAttributeOverrides(Class cls, AnnotationsDescriptor descriptor) {
        // Look for an @AttributeOverrides.
        AttributeOverrides attributeOverrides = AnnotationsHelper.getAnnotation(AttributeOverrides.class, cls, descriptor);	
        if (attributeOverrides != null) {
            for (AttributeOverride attributeOverride : attributeOverrides.value()) {
                processAttributeOverride(attributeOverride.name(), attributeOverride.column(), descriptor);
            }
        }
        
        // Look for an @AttributeOverride.
        AttributeOverride attributeOverride = AnnotationsHelper.getAnnotation(AttributeOverride.class, cls, descriptor);
        if (attributeOverride != null) {
            processAttributeOverride(attributeOverride.name(), attributeOverride.column(), descriptor);
        }
    }
    
    /**
     * INTERNAL:
     * Process an @AttributeOverrides for an embedded object, that is, an
     * aggregate object mapping in TopLink. 
     * 
     * It will also look for an @AttributeOverride.
     */
    protected void processAttributeOverrides(MetadataAccessor accessor, AggregateObjectMapping mapping) {
        // Look for an @AttributeOverrides.
        AttributeOverrides attributeOverrides = AnnotationsHelper.getAnnotation(AttributeOverrides.class, accessor);
        
        if (attributeOverrides != null) {
            for (AttributeOverride attributeOverride : attributeOverrides.value()) {
                processAttributeOverride(attributeOverride.name(), attributeOverride.column(), accessor, mapping);
            }
        }
        
        // Look for an @AttributeOverride.
        AttributeOverride attributeOverride = AnnotationsHelper.getAnnotation(AttributeOverride.class, accessor);	
        if (attributeOverride != null) {
            processAttributeOverride(attributeOverride.name(), attributeOverride.column(), accessor, mapping);
        }
    }
    
    /**
     * INTERNAL:
     * Process a @Basic.
     */
    protected void processBasic(MetadataAccessor accessor, DirectToFieldMapping mapping) {
        Basic basic = AnnotationsHelper.getAnnotation(Basic.class, accessor);
        
        if (basic != null) {
            // Basic.fetch()
            if (basic.fetch() == FetchType.LAZY) {
                getLogger().logWarningMessage(AnnotationsLogger.IGNORE_BASIC_FETCH_LAZY, accessor);
            }
        
            // Basic.optional()    
            mapping.setIsOptional(basic.optional());
        }
    }
    
    /**
     * INTERNAL:
     * Process the array of methods for lifecyle callback events and set them
     * on the given event listener.
     */
    protected void processCallbackMethods(Method[] candidateMethods, MetadataEntityListener listener, AnnotationsDescriptor descriptor) {
        for (Method method : candidateMethods) {
            processPostLoad(method, listener, descriptor);
            processPostPersist(method, listener, descriptor);
            processPostRemove(method, listener, descriptor);
            processPostUpdate(method, listener, descriptor);
            processPrePersist(method, listener, descriptor);
            processPreRemove(method, listener, descriptor);
            processPreUpdate(method, listener, descriptor);
        }
    }
    
    /**
     * INTERNAL:
     * Process the cascade type on a mapping.
     */
	protected void processCascadeType(MetadataAccessor accessor, Object[] types, ForeignReferenceMapping mapping) {
		CascadeType[] cTypes = (CascadeType[]) types;
		for (CascadeType type : cTypes) {
			setCascadeType(type.name(), mapping);
		}
        
        // Apply the persistence unit default cascade-persist if necessary.
        if (accessor.getMetadataDescriptor().isCascadePersistSet() && !mapping.isCascadePersist()) {
        	setCascadeType(MetadataConstants.PERSIST, mapping);
        }
	}
    
    /**
     * INTERNAL:
     * Process a @Column into a DatabaseField and return it. Assumes the column
     * is not null at this point.
     */
    protected DatabaseField processColumn(Column column, AnnotatedElement annotatedElement, String attributeName, AnnotationsDescriptor descriptor) {
		return processColumn(attributeName, column.name(), column.columnDefinition(), column.table(), 
        		column.unique(), column.nullable(), column.insertable(), column.updatable(), 
        		column.length(), column.precision(), column.scale(), annotatedElement, descriptor);
	}
    
    /**
     * INTERNAL:
     * Process a @Column for the given accessor into a DatabaseField. If there 
     * is no @Column, we will default one.
     */
    protected DatabaseField processColumn(MetadataAccessor accessor) {
        MetadataDescriptor descriptor = accessor.getMetadataDescriptor();
        String attributeName = accessor.getAttributeName();
        AnnotatedElement annotatedElement = (AnnotatedElement) accessor.getAnnotatedElement();
        
        // Process the @Column if there is one othersie return a defaulted one.
        Column column = AnnotationsHelper.getAnnotation(Column.class, accessor);
        if (column == null) {
            return processColumnDefaults(attributeName, descriptor, annotatedElement);
        } else {
            return processColumn(column, annotatedElement, attributeName, descriptor);
        }
    }

    /**
     * INTERNAL:
     * This method is called when processing an @AttributeOverride. The column 
     * can not be null at this point.
     */
    protected DatabaseField processColumn(Object column, Object annotatedElement, String attributeName, MetadataDescriptor descriptor) {
    	return processColumn((Column) column, (AnnotatedElement) annotatedElement, attributeName, (AnnotationsDescriptor) descriptor);
    }
    
    /**
     * INTERNAL:
     * Process the default listeners defined XML. This method will process the
     * class for additional lifecycle callback methods that are decorated with
     * annotations.
     * 
     * NOTE: We add the default listeners regardless if the exclude default 
     * listeners flag is set. This allows the user to change the exlcude flag 
     * at runtime and have the default listeners available to them.
     */
    protected void processDefaultListeners(AnnotationsDescriptor descriptor) {
        for (MetadataEntityListener defaultListener : (List<MetadataEntityListener>) descriptor.getDefaultEventListeners()) {
            // Init the default listener with what we found in XML.
            defaultListener.initializeCallbackMethods(m_loader);
            
            // Process the candidate callback methods on this listener for
            // additional callback methods decorated with annotations.
            processCallbackMethods(MetadataHelper.getCandidateCallbackMethodsForDefaultListener(defaultListener), defaultListener, descriptor);
        }
    }
    
    /**
     * INTERNAL:
     * Process a @DiscriminatorColumn (if there is one, otherwise default) to 
     * set this classes indication field name for inheritance.
     */
    protected void processDiscriminatorColumn(AnnotationsDescriptor descriptor) {
        DiscriminatorColumn discriminatorColumn = AnnotationsHelper.getAnnotation(DiscriminatorColumn.class, descriptor);
        
        int length = 31;
        String name = "";
        String columnDefinition = "";
        String discriminatorType = MetadataConstants.STRING;
        
        if (discriminatorColumn != null) {
            // DiscriminatorColumn.name(), might default to ""
            name = discriminatorColumn.name();   
            
            // DiscriminatorColumn.discriminatorType()
            discriminatorType = discriminatorColumn.discriminatorType().toString();
            
            // DiscriminatorColumn.columnDefinition()
            columnDefinition = discriminatorColumn.columnDefinition();
            
            // DiscriminatorColumn.length()
            length = discriminatorColumn.length();
        }

        processDiscriminatorColumn(name, columnDefinition, length, discriminatorType, descriptor);
    }
    
    /**
     * INTERNAL:
     * Process a @DiscriminatorValue to set the class indicator on the root 
     * descriptor of the inheritance hierarchy. 
     * If there is no @DiscriminatorValue, the class indicator defaults to 
     * the class name.
	 */
	protected void processDiscriminatorValue(MetadataDescriptor descriptor) {
        DiscriminatorValue discriminatorValue = AnnotationsHelper.getAnnotation(DiscriminatorValue.class, descriptor);
        String value = (discriminatorValue == null) ? null : discriminatorValue.value();
        processDiscriminatorValue(value, descriptor);
	}
            
    /**
     * INTERNAL:
     * Process the @MappedSuperclass(es) if there are any. There may be
     * several MappedSuperclasses for any given Entity.
     */
    protected void processMappedSuperclasses(AnnotationsDescriptor descriptor) {
        for (Class mappedSuperclass : (ArrayList<Class>) descriptor.getMappedSuperclasses()) {
            // Process the @NamedQueries and @NamedQuery.
            processNamedQueries(mappedSuperclass, descriptor);
                    
            // Process the @NamedNativeQueries and @NamedNativeQuery.
            processNamedNativeQueries(mappedSuperclass, descriptor);
            
            // Process the @SqlRessultSetMapping.
            processSqlResultSetMappings(mappedSuperclass, descriptor);
            
            // Process the accessors from the mapped superclass.
            processAccessors(mappedSuperclass, descriptor.usesPropertyAccess(), descriptor);
            
            // Process the attribute overrides on this MappedSuperclass, if
            // there are any before processing its parent MappedSuperclass.
            processAttributeOverrides(mappedSuperclass, descriptor);
        }
    }
    
    /**
     * INTERNAL:
     * Process the items of interest on an entity class. The order of 
     * processing is important, care must be taken if changes must be made.
     * 
     * Classes without an @Entity are ignored if no metadata complete flag
     * is set.
     */
    protected void processEntityClass(Class cls) {
        String entityName = "";
        AnnotationsDescriptor descriptor = null;
        
        // Check for metadata complete flag.
        if (AnnotationsHelper.shouldIgnoreAnnotations(cls, m_metadataDescriptors)) {
            descriptor = getMetadataDescriptor(cls);
        } else {
            // Othwerwise look for an @Entity .
            Entity entity = AnnotationsHelper.getAnnotation(Entity.class, cls);
            if (entity != null) {
                entityName = entity.name();
                descriptor = getMetadataDescriptor(cls);
            } 
        }
        
        // If we have no descriptor metadata or if it is an aggregate 
        // descriptor, then ignore the processing for this class.
        //if (descriptor != null && ! descriptor.isAggregate() && ! descriptor.isProcessed()) {
        if (descriptor != null && ! descriptor.isProcessed()) {
            // Set the ignore flags for the annotations that should not be 
            // processed since data is already defined on the descriptor. Our 
            // XML/Annotation merge strategy is XML wins! We assume that any 
            // data in the XML is properly and fully defined.
            descriptor.setIgnoreFlags();
                   
            // Process the @Entity.
            // Any root inheritance processing is fast tracked in this call.
            processEntity(entityName, descriptor);
                    
            // Process the @NamedQueries and @NamedQuery.
            processNamedQueries(descriptor);
                    
            // Process the @NamedNativeQueries and @NamedNativeQuery.
            processNamedNativeQueries(descriptor);
                    
            // Process the @SqlRessultSetMapping.
            processSqlResultSetMappings(descriptor);
                    
            // Pre-process the sequencing annotations.
            preProcessSequencing(cls, descriptor);
                    
            // Process the @Table if there is one. Must be processed before the 
            // calls below.
            processTable(descriptor);
                    
            // Process the @IdClass (pkClass).
            processIdClass(descriptor);
                    
            // Set our attribute overrides from the entity class before 
            // processing the accessors from the mapped superclass.
            processAttributeOverrides(cls, descriptor);
                    
            // Set our association overrides from the entity class before 
            // processing the accessors from the mapped superclass.
            processAssociationOverrides(cls, descriptor);
                
            // Process the @MappedSuperclass(es).
            processMappedSuperclasses(descriptor);
                    
            // Process the accessors on this entity now.
            processAccessors(descriptor);

            // handle defaulted primary key field name if necessary
            if (descriptor.isDefaultPrimaryKeySet()) {
            	adjustPrimaryKeyNameOnExistingFields(descriptor);
            }
                    
            // If this descriptor has a composite primary key, check that all 
            // our composite primary key attributes were validated. 
            if (descriptor.hasCompositePrimaryKey()) {
                if (descriptor.pkClassWasNotValidated()) {
                    getValidator().throwInvalidCompositePKSpecification(cls, descriptor.getPKClassName());
                }
            } else {
                // Descriptor has a single primary key. Validate an id 
                // attribute was found, unless we are an inheritance subclass.
                if (! descriptor.hasPrimaryKeyFields() && !descriptor.isInheritanceSubclass()) {
                    getValidator().throwNoPrimaryKeyAnnotationsFound(cls);
                }
            }
                    
            // Process the @SecondaryTable(s).
            processSecondaryTables(descriptor);          
        }
    }
    
    /**
     * INTERNAL:
     * Process the entity class for lifecycle callback event methods. This
     * method will traverse the @MappedSuperclasses as well. 
     * 
     * NOTE: This method will not overwrite or append to a listener that was 
     * specified in XML.
     */
    protected void processEntityEventListener(AnnotationsDescriptor descriptor) {
        Class entityClass = descriptor.getJavaClass();
        
        // Check for lifecycle callback methods on the entity itself and its 
        // mapped superclasses, unless XML already specified them.
        if (descriptor.hasEntityEventListener()) {
            MetadataEntityListener entityEventListener = descriptor.getEntityEventListener();
            
            // Init the entity event listener with what we found in XML.
            entityEventListener.initializeCallbackMethods(m_loader);
        } else {
            AnnotationsEntityClassListener listener = new AnnotationsEntityClassListener(entityClass);
                
            // Check the mapped superclasses.
            for (Class mappedSuperclass : (ArrayList<Class>) descriptor.getMappedSuperclasses()) {
                processCallbackMethods(MetadataHelper.getCandidateCallbackMethodsForMappedSuperclass(mappedSuperclass, entityClass), listener, descriptor);
            }
                
            // Check the entity class.
            processCallbackMethods(MetadataHelper.getCandidateCallbackMethodsForEntityClass(entityClass), listener, descriptor);
        
            // Add the listener only if we found callback methods.
            if (listener.hasCallbackMethods()) {
                descriptor.setEntityEventListener(listener);
            }
        }
    }
    
    /**
     * INTERNAL:
     * Process the @EntityListeners for this entity class. If entity listeners
     * have been specified via XML, this method will process only those entity 
     * listener classes specified from XML (taking metadata-complete into 
     * consideration) for additional lifecycle callback methods that are 
     * decorated with annotations. That is, it ignores its and its mapped
     * superclasses, @EntityListeners annotations.
     */
    protected void processEntityListeners(AnnotationsDescriptor descriptor) {
        Class entityClass = descriptor.getJavaClass();
        
        // Check for @EntityListeners unless XML already specified them.
        if (descriptor.hasEntityListenerEventListeners()) {
            // We have entity listeners defined in XML, don't overrite them. 
            for (MetadataEntityListener entityListener : (List<MetadataEntityListener>) descriptor.getEntityListenerEventListeners()) {
                // Init the default listener with what we found in XML.
                entityListener.initializeCallbackMethods(m_loader);
            
                // Process the candidate callback methods on this listener for
                // additional callback methods decorated with annotations.
                processCallbackMethods(MetadataHelper.getCandidateCallbackMethodsForEntityListener(entityListener), entityListener, descriptor);
            }
        } else if (AnnotationsHelper.isAnnotationPresent(EntityListeners.class, descriptor)) {
            // Process @EntityListeners defined on the entity class and mapped
            // superclasses (taking metadata-complete into consideration).
            // Go through the mapped superclasses first, top -> down only if
            // the exclude superclass listeners flag is not set.    
            if (! descriptor.excludeSuperclassListeners()) {
                List mappedSuperclasses = descriptor.getMappedSuperclasses();
                int mappedSuperclassesSize = mappedSuperclasses.size();
            
                for (int i = mappedSuperclassesSize - 1; i >= 0; i--) {
                    Class mappedSuperclass = (Class) mappedSuperclasses.get(i);
                    EntityListeners entityListeners = AnnotationsHelper.getAnnotation(EntityListeners.class, mappedSuperclass, descriptor);
                    processEntityListeners(entityListeners, entityClass, descriptor);
                }
            }
                    
            // Check the entity class for @EntityListeners.
            EntityListeners entityListeners = AnnotationsHelper.getAnnotation(EntityListeners.class, descriptor);
            processEntityListeners(entityListeners, entityClass, descriptor);
        }
    }
    
    /**
     * INTERNAL:
     * Process and entity listener for lifecycle callback methods.
     */
    protected void processEntityListeners(EntityListeners entityListeners, Class entityClass, AnnotationsDescriptor descriptor) {
        if (entityListeners != null) {
            for (Class entityListener : entityListeners.value()) {
                MetadataEntityListener listener = new MetadataEntityListener(entityListener, entityClass);
                
                // Process the candidate callback methods for this listener ...
                processCallbackMethods(MetadataHelper.getCandidateCallbackMethodsForEntityListener(listener), listener, descriptor);
                
                // Add the entity listener to the descriptor event manager.    
                descriptor.addEntityListenerEventListener(listener);
            }
        }
    }
    
    /**
     * INTERNAL:
     * Process an @Enumerated. The method may still be called if no @Enumerated
     * has been specified but the accessor's reference class is a valid 
     * enumerated type.
     */
    protected void processEnumerated(MetadataAccessor accessor, DirectToFieldMapping mapping) {
        boolean isOrdinal = true;   // default
        
        // If we have an @Enumerated annotation get the enumerated type.
        if (accessor.hasEnumerated()) {
	        Enumerated enumerated = AnnotationsHelper.getAnnotation(Enumerated.class, accessor);
	        isOrdinal = (enumerated.value() == EnumType.ORDINAL);
        }
        
        processEnumerated(accessor, isOrdinal, mapping);
    }
    
    /**
     * INTERNAL:
     * Process the @ExcludeDefaultListeners only if one is specified (taking
     * metadata-complete into consideration), otherwise use what is specified 
     * from XML (XML processor will set a value).
     */
    protected void processExcludeDefaultListeners(AnnotationsDescriptor descriptor) {
        // Set the @ExcludeDefaultListeners flag. Don't overwrite a 'true' 
        // value XML default though if this annotation is not present.
        if (AnnotationsHelper.isAnnotationPresent(ExcludeDefaultListeners.class, descriptor)) {            
            descriptor.setExcludeDefaultListeners(true);
        } 
    }
    
    /**
     * INTERNAL:
     * Process the @ExcludeSuperclassListeners only if one is specified (taking
     * metadata-complete into consideration), otherwise use what is specified 
     * from XML (XML processor will set a value).
     */
    protected void processExcludeSuperclassListeners(AnnotationsDescriptor descriptor) {
        // Set the @ExcludeSuperclassListeners flag. Don't overwrite a 
        // 'true' value XML default though if this annotation is not present.
        if (AnnotationsHelper.isAnnotationPresent(ExcludeSuperclassListeners.class, descriptor)) {
            descriptor.setExcludeSuperclassListeners(true);    
        }
    }
        
    /**
     * INTERNAL:
     * Process an @Id if there is one.
     */
    protected void processId(MetadataAccessor accessor, DatabaseField field) {
        Id id = AnnotationsHelper.getAnnotation(Id.class, accessor);
        
        if (id != null) {
            processId(accessor, field, AnnotationsHelper.getAnnotation(GeneratedValue.class, accessor));
        }
    }
	
    /**
     * INTERNAL:
     * Process an @IdClass. 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(AnnotationsDescriptor descriptor) {
    	IdClass idClass = AnnotationsHelper.getAnnotation(IdClass.class, descriptor);
        
        if (idClass != null) {
            // IdClass.value(), our pk class.
            processIdClass(idClass.value(), descriptor);
        }
    }
    
    /**
     * INTERNAL:
     * Process an @Inheritance if there is one. An @Inheritance may or may not
     * be specified on the entity class that is the root of then entity class
     * hierarchy, therefore, we need to check the flag on the metadata
     * descriptor.
     */
    protected void processInheritanceRoot(MetadataDescriptor metadataDescriptor) {
        AnnotationsDescriptor descriptor = (AnnotationsDescriptor) metadataDescriptor;
        Inheritance inheritance = AnnotationsHelper.getAnnotation(Inheritance.class, descriptor);
        
        if (inheritance != null || descriptor.isInheritanceRoot()) {
            if (descriptor.ignoreInheritanceAnnotations()) {
                // XML/Annotation merging. XML wins, ignore annotations.
                getLogger().logWarningMessage(AnnotationsLogger.IGNORE_INHERITANCE, descriptor);
            } else {
                // Inheritance.strategy(), store on the descriptor metadata.
                String inheritanceStrategy = (inheritance == null) ? MetadataConstants.SINGLE_TABLE : inheritance.strategy().toString();
                descriptor.setInheritanceStrategy(inheritanceStrategy);
                
                // Process the @DiscriminatorColumn.
                processDiscriminatorColumn(descriptor);
                
                // Process the @DiscriminatorValue.
                processDiscriminatorValue(descriptor);
            }
        }
    }
    
    /**
     * INTERNAL:
     * Process a @JoinColumn. Returns a MetadataJoinColumn object. 
     */
     protected MetadataJoinColumn processJoinColumn(JoinColumn joinColumn, MetadataAccessor accessor, String targetTable, MetadataDescriptor sourceDescriptor) {
    	return processJoinColumn(
    			accessor,
    			targetTable,
    			sourceDescriptor,
    			joinColumn.name(),
    			joinColumn.referencedColumnName(),
    			joinColumn.columnDefinition(),
    			joinColumn.table(),
    			joinColumn.unique(),
    			joinColumn.nullable(),
    			joinColumn.insertable(),
    			joinColumn.updatable());
    }
    
    /**
     * INTERNAL:
     * Process a @JoinColumns or @JoinColumn based on if the descriptor metadata 
     * from the accessor uses a composite primary key or not.
     * Returns a Vector of MetadataJoinColumn.
     */	
    protected ArrayList<MetadataJoinColumn> processJoinColumns(MetadataAccessor ma) {
        ArrayList<MetadataJoinColumn> allJoinColumns = new ArrayList<MetadataJoinColumn>();
        
        AnnotationsAccessor accessor = (AnnotationsAccessor) ma;
        MetadataDescriptor descriptor = accessor.getMetadataDescriptor();
        String targetTableName = descriptor.getPrimaryTableName();
        MetadataDescriptor referenceDescriptor = accessor.getReferenceMetadataDescriptor();
        String sourceTableName = referenceDescriptor.getPrimaryTableName();
        
        if (referenceDescriptor.hasCompositePrimaryKey()) {
            // Composite primary key, look for a @JoinColumns.
            JoinColumn[] joinColumns = new JoinColumn[]{};
            if (descriptor.hasAssociationOverrideFor(accessor)) {
                joinColumns = (JoinColumn[]) descriptor.getAssociationOverrideFor(accessor);
            } else {
                JoinColumns jcs = AnnotationsHelper.getAnnotation(JoinColumns.class, accessor);
                
                if (jcs == null) {
                    getValidator().throwIncompleteJoinColumnsSpecified(accessor.getJavaClass(), accessor.getAnnotatedElement());
                } else {
                    joinColumns = jcs.value();
                }
            }
            
            // The number of JoinColumn specified should equal the number of 
            // primary key fields.
            if (joinColumns.length != referenceDescriptor.getPrimaryKeyFields().size()) {
                getValidator().throwIncompleteJoinColumnsSpecified(accessor.getJavaClass(), accessor.getAnnotatedElement());
            }
            
            for (JoinColumn joinColumn : joinColumns) {          
                allJoinColumns.add(processJoinColumn(joinColumn, accessor, targetTableName, referenceDescriptor));
            }
        } else {
            // Single primary key, look for a @JoinColumn.
            JoinColumn joinColumn;
            if (descriptor.hasAssociationOverrideFor(accessor)) {
                joinColumn = ((JoinColumn[]) descriptor.getAssociationOverrideFor(accessor))[0];
            } else {
                joinColumn = AnnotationsHelper.getAnnotation(JoinColumn.class, accessor);
            }
            
            if (joinColumn == null) {
                if (accessor.hasJoinColumns()) {
                    getValidator().throwExcessiveJoinColumnsSpecified(accessor.getJavaClass(), accessor.getAnnotatedElement());
                }
                
                addJoinColumnDefault(allJoinColumns, sourceTableName, targetTableName, descriptor);
            } else {
                allJoinColumns.add(processJoinColumn(joinColumn, accessor, targetTableName, referenceDescriptor));
            } 
        }
            
        return allJoinColumns;
    }
    
    /**
     * INTERNAL:
     * Process a @ManyToMany into a TopLink ManyToMany mapping. The method 
     * assumes there is actually a ManyToMany annotation on the annotated
     * element. 
     */
    protected void processManyToMany(MetadataAccessor accessor) {
        ManyToMany manyToMany = AnnotationsHelper.getAnnotation(ManyToMany.class, accessor);
        processManyToMany(accessor, manyToMany.targetEntity(), manyToMany.cascade(), manyToMany.fetch() == FetchType.LAZY, manyToMany.mappedBy());
    }
	
    /**
     * INTERNAL:
     * Process a @ManyToOne into a TopLink OneToOne mapping. The method assumes 
     * there is actually a ManyToOne annotation on the annotated element. 
     * 
     * Note: Target foreign keys are not valid for m-1 mapping
     */
    protected void processManyToOne(MetadataAccessor accessor) {
        ManyToOne manyToOne = AnnotationsHelper.getAnnotation(ManyToOne.class, accessor);
        processManyToOne(accessor, manyToOne.targetEntity(), manyToOne.cascade(), manyToOne.fetch() == FetchType.LAZY, manyToOne.optional());
    }
    
    /**
     * INTERNAL:
     * Process a @MapKey for a 1-M or M-M mapping. Will return the map key name 
     * that should be used.
     */
    protected String processMapKey(MetadataAccessor accessor, CollectionMapping mapping) {
        MapKey mapKey = AnnotationsHelper.getAnnotation(MapKey.class, accessor);
        String mapKeyName = (mapKey == null) ? "" : mapKey.name();
        
        return processMapKey(accessor, mapping, mapKeyName);
    }
    
    /**
     * INTERNAL:
     * Process a @NamedNativeQueries. The method will also look for 
     * a @NamedNativeQuery. This method currently only stores the queries if 
     * there are some. The actually query processing isn't done till 
     * addNamedQueriesToSession is called.
     */
    protected void processNamedNativeQueries(AnnotationsDescriptor descriptor) {
        processNamedNativeQueries(descriptor.getJavaClass(), descriptor);
    }
    
    /**
     * INTERNAL:
     * Process a @NamedNativeQueries. The method will also look for 
     * a @NamedNativeQuery. This method currently only stores the queries if 
     * there are some. The actually query processing isn't done till 
     * addNamedQueriesToSession is called.
     */
    protected void processNamedNativeQueries(Class entityClass, AnnotationsDescriptor descriptor) {
        // Look for a NamedNativeQueries annotation.
        NamedNativeQueries namedNativeQueries = AnnotationsHelper.getAnnotation(NamedNativeQueries.class, entityClass, descriptor);

        if (namedNativeQueries != null) {
            for (NamedNativeQuery namedNativeQuery : namedNativeQueries.value()) {
                descriptor.addNamedNativeQuery(namedNativeQuery);
            }
            
            m_entitiesWithQueries.add(descriptor);
        }
        
        // Look for a NamedNativeQuery annotation.
        NamedNativeQuery namedNativeQuery = AnnotationsHelper.getAnnotation(NamedNativeQuery.class, entityClass, descriptor);
        
        if (namedNativeQuery != null) {
            descriptor.addNamedNativeQuery(namedNativeQuery);
            m_entitiesWithQueries.add(descriptor);
        }
    }
    
    /**
     * INTERNAL:
     * Does the actual NamedNativeQuery validation and adds the query to the 
     * session.
     */
    protected void processNamedNativeQuery(NamedNativeQuery namedNativeQuery, AnnotationsDescriptor descriptor, AbstractSession session) {
        // NamedNativeQuery.name()
        String name = namedNativeQuery.name();
           
        if (session.getQuery(name) != null) {
            // XML/Annotation merging. XML wins, ignore annotations.
            getLogger().logWarningMessage(AnnotationsLogger.IGNORE_QUERY, descriptor, name);
        } else {
            // NamedNativeQuery.query(), contains the sql string.
            String queryString = namedNativeQuery.query();
            
            // NamedNativeQuery.hints()
            HashMap hints = processQueryHints(namedNativeQuery.hints());
        
            // NamedNativeQuery.resultClass()
            Class resultClass = namedNativeQuery.resultClass();
            if (resultClass != void.class) {
                resultClass = MetadataHelper.getClassForName(resultClass.getName(), m_loader);
                session.addQuery(name, EJBQueryImpl.buildSQLDatabaseQuery(resultClass, queryString, hints));
                return;
            } 
            
            // NamedNativeQuery.resultSetMapping(), name of SQLResultSetMapping
            String resultSetMapping = namedNativeQuery.resultSetMapping();
            if (! resultSetMapping.equals("")) {
                session.addQuery(name, EJBQueryImpl.buildSQLDatabaseQuery(resultSetMapping, queryString, hints));
                return;
            } 
            
            // Neither a resultClass or resultSetMapping is specified so place in a temp query on the session
            session.addQuery(name, EJBQueryImpl.buildSQLDatabaseQuery( queryString, hints));
        }
    }
    
    /**
     * INTERNAL:
     * Process a @NamedQueries. The method will also look for a @NamedQuery.
     * This method currently only stores the queries if there are some. The
     * actually query processing isn't done till addNamedQueriesToSession is
     * called.
     */
    protected void processNamedQueries(AnnotationsDescriptor descriptor) {
        processNamedQueries(descriptor.getJavaClass(), descriptor);
    }
    
    /**
     * INTERNAL:
     * Process a @NamedQueries. The method will also look for a @NamedQuery.
     * This method currently only stores the queries if there are some. The
     * actually query processing isn't done till addNamedQueriesToSession is
     * called.
     */
    protected void processNamedQueries(Class entityClass, AnnotationsDescriptor descriptor) {
        // Look for a NamedQueries annotation.
        NamedQueries namedQueries = AnnotationsHelper.getAnnotation(NamedQueries.class, entityClass, descriptor);
        
        if (namedQueries != null) {
            for (NamedQuery namedQuery : namedQueries.value()) {
                descriptor.addNamedQuery(namedQuery);
            }
            
            m_entitiesWithQueries.add(descriptor);
        }
        
        // Look for a NamedQuery annotation.
        NamedQuery namedQuery = AnnotationsHelper.getAnnotation(NamedQuery.class, entityClass, descriptor);
        
        if (namedQuery != null) {
            descriptor.addNamedQuery(namedQuery);
            m_entitiesWithQueries.add(descriptor);
        }
    }
    
    /**
     * INTERNAL:
     * Does the actual NamedQuery validation and adds the query to the session.
     */
    protected void processNamedQuery(NamedQuery namedQuery, AnnotationsDescriptor descriptor, AbstractSession session) {
        // NamedQuery.name(), is the name of the query.
    	String name = namedQuery.name();
           
        if (session.getQuery(name) != null) {
            getLogger().logWarningMessage(AnnotationsLogger.IGNORE_QUERY, descriptor, name);
        } else {
            try {
                // NamedQuery.query(), contains the ejbql string.
                String queryString = namedQuery.query();
            
                // NamedQuery.hints()
                HashMap hints = processQueryHints(namedQuery.hints());
            
                // Add the query.
                session.addEjbqlPlaceHolderQuery(new EJBQLPlaceHolderQuery(name, queryString, hints));
            } catch (Exception exception) {
                getValidator().throwErrorProcessingNamedQueryAnnotation(descriptor.getJavaClass(), name, exception);
            }
        }
    }
    
    /**
     * INTERNAL:
     * Process a @OneToMany into a TopLink OneToMany mapping. If an @JoinTable 
     * is found however, we must create a ManyToMany mapping.
     */
    protected void processOneToMany(MetadataAccessor accessor) {
        // Extract the annotation, if there is one. We must null check.	
        OneToMany oneToMany = AnnotationsHelper.getAnnotation(OneToMany.class, accessor);
        String mappedBy = "";
        
        if (oneToMany != null) {
        	mappedBy = oneToMany.mappedBy();
        }
    	
        processOneToMany(accessor, oneToMany, mappedBy);
    }
	
    /**
     * INTERNAL:
     * Process a @OneToOne into a TopLink OneToOne mapping.
     */
    protected void processOneToOne(MetadataAccessor accessor) {
        // Extract the annotation, if there is one. We must null check.	
        OneToOne oneToOne = AnnotationsHelper.getAnnotation(OneToOne.class, accessor);
		
        // Process the annotation specifics if there is one (may have been
        // defaulted). These are our defaults otherwise.
        String mappedBy = "";
        Class targetEntity = void.class;
        CascadeType[] cascade = {};
        boolean optional = true; 
        boolean usesIndirection = false;
        
        if (oneToOne != null) {
            // OneToOne.targetEntity()
            targetEntity = oneToOne.targetEntity();
            // OneToOne.cascade()
            cascade = oneToOne.cascade();
            // OneToOne.fetch()
            usesIndirection = oneToOne.fetch() == FetchType.LAZY;
            // OneToOne.optional()
            optional = oneToOne.optional();
            // OneToOne.mappedBy()
            mappedBy = oneToOne.mappedBy();
        }
	
        processOneToOne(accessor, targetEntity, cascade, usesIndirection, optional, mappedBy);
    }
		
    /**
     * INTERNAL:
     * Process the annotations and fill in the project. Process in 2 steps, 
     * first step to process most metadata except relationship mappings, second 
     * step to go through the classes that have relationships and fill in the 
     * rest.
     * 
     * Assumes only Entity classes have been set for processing.
     */
    public AbstractSession processORAnnotations() {
        // Step 1, fill in most of the metadata.
        for (Class cls: m_classes) {
            // Process the entity class.
            processEntityClass(cls);
        }
        
        // Step 1.5, sequencing setup. 
        m_sequencingProcessor.process(m_session.getProject().getLogin());
        
        // Step 2, fill in the relationships.
        for (AnnotationsDescriptor descriptor : (HashSet<AnnotationsDescriptor>) m_relatedEntities) {
            processRelatedEntity(descriptor);
        }
		
        // WIP - remove this code as it should not be necessary.
        // remove from aliased descriptors the one named "" - it's there under another name
        Map aliasDescriptors = m_session.getProject().getAliasDescriptors();
        if (aliasDescriptors != null) {
            aliasDescriptors.remove("");
        }
        
        // Return the modified session and we are done!
        return m_session;
    }
    
    /**
     * INTERNAL:
     * Process the @OrderBy annotation 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) { 
        OrderBy orderBy = AnnotationsHelper.getAnnotation(OrderBy.class, accessor);
        
        if (orderBy != null) {
            processOrderBy(accessor, mapping, orderBy.value());
        }
    }
    
    /**
     * INTERNAL:
     * Process a @PostLoad.
     * 
     * The PostLoad method for an entity is invoked after the entity has been 
     * loaded into the current persistence context from the database or after 
     * the refresh operation has been applied to it. The PostLoad method is 
     * invoked before a query result is returned or accessed or before an 
     * association is traversed.
     */
    protected void processPostLoad(Method method, MetadataEntityListener listener, AnnotationsDescriptor descriptor) {
        if (AnnotationsHelper.isAnnotationPresent(PostLoad.class, method, descriptor)) {
            listener.setPostBuildMethod(method);
            listener.setPostCloneMethod(method);
            listener.setPostRefreshMethod(method);
        }
    }
    
    /**
     * INTERNAL:
     * Process a @PostPersist.
     * 
     * The PostPersist callback method is invoked for an entity after the 
     * EntityManager persist operations for that entity is executed. The 
     * callback will also be invoked on all entities to which these operations 
     * are cascaded. The PostPersist method will be invoked after the database 
     * insert operation. This may be directly after the persist operation has 
     * been invoked or it may be directly after a flush operation has occurred 
     * or it may be at the end of the transaction. Exceptions thrown by this 
     * callback cause the current transaction to be rolled back.
     */
    protected void processPostPersist(Method method,  MetadataEntityListener listener, AnnotationsDescriptor descriptor) {
        if (AnnotationsHelper.isAnnotationPresent(PostPersist.class, method, descriptor)) {
            listener.setPostInsertMethod(method);
        } 
    }
    
    /**
     * INTERNAL:
     * Process a @PostRemove.
     * 
     * The PostRemove callback method is invoked for an entity after the 
     * EntityManager remove operations for that entity is executed. The 
     * callback will also be invoked on all entities to which these operations 
     * are cascaded. The PostRemove method will be invoked after the database 
     * delete operation. This may be directly after the remove operation has 
     * been invoked or it may be directly after a flush operation has occurred 
     * or it may be at the end of the transaction. Exceptions thrown by this 
     * callback cause the current transaction to be rolled back.
     */
    protected void processPostRemove(Method method,  MetadataEntityListener listener, AnnotationsDescriptor descriptor) {
        if (AnnotationsHelper.isAnnotationPresent(PostRemove.class, method, descriptor)) {
            listener.setPostDeleteMethod(method);
        } 
    }
    
    /**
     * INTERNAL:
     * Process a @PostUpdate.
     * 
     * The PostUpdate callback occurs after the database update operations to 
     * entity data. This may be at the time the entity state is updated or it 
     * may be at the time state is flushed to the database or at the end of the 
     * transaction.
     */
    protected void processPostUpdate(Method method,  MetadataEntityListener listener, AnnotationsDescriptor descriptor) {
        if (AnnotationsHelper.isAnnotationPresent(PostUpdate.class, method, descriptor)) {
            listener.setPostUpdateMethod(method);
        } 
    }
            
    /**
     * INTERNAL:
     * Process a @PrePersist.
     * 
     * The PrePersist callback method is invoked for a given entity before the 
     * EntityManager persist operations for that entity are executed. This 
     * callback will also be invoked on all entities to which these operations 
     * are cascaded. The PrePersist method will always be invoked as part of 
     * the synchronous persist operation. Exceptions thrown by this callback 
     * cause the current transaction to be rolled back.
     */
    protected void processPrePersist(Method method,  MetadataEntityListener listener, AnnotationsDescriptor descriptor) {
        if (AnnotationsHelper.isAnnotationPresent(PrePersist.class, method, descriptor)) {
            listener.setPrePersistMethod(method);
        } 
    }
    
    /**
     * INTERNAL:
     * Process a @PreRemove.
     * 
     * The PreRemove callback method is invoked for a given entity before the 
     * EntityManager remove operations for that entity are executed. This 
     * callback will also be invoked on all entities to which these operations 
     * are cascaded. The PreRemove methods will always be invoked as part of 
     * the synchronous persist and remove operations. Exceptions thrown by this 
     * callback cause the current transaction to be rolled back.
     */
    protected void processPreRemove(Method method,  MetadataEntityListener listener, AnnotationsDescriptor descriptor) {
        if (AnnotationsHelper.isAnnotationPresent(PreRemove.class, method, descriptor)) {
            listener.setPreRemoveMethod(method);
        }
    }
    
    /**
     * INTERNAL:
     * Process a @PreUpdate.
     * 
     * The PreUpdate callback occurs before database update operations to 
     * entity data. This may be at the time the entity state is updated or it 
     * may be at the time state is flushed to the database or at the end of the 
     * transaction.
     */
    protected void processPreUpdate(Method method,  MetadataEntityListener listener, AnnotationsDescriptor descriptor) {
        if (AnnotationsHelper.isAnnotationPresent(PreUpdate.class, method, descriptor)) {
            listener.setPreUpdateWithChangesMethod(method);
        }
    }
    
    /**
     * INTERNAL:
     */
    protected ArrayList<MetadataJoinColumn> processPrimaryKeyJoinColumns(Object[] primaryKeyJoinColumns, String sourceTableName, String targetTableName, MetadataDescriptor descriptor) {
    	ArrayList<MetadataJoinColumn> metadataJoinColumns = new ArrayList<MetadataJoinColumn>();
        
	    for (PrimaryKeyJoinColumn primaryKeyJoinColumn : (PrimaryKeyJoinColumn[]) primaryKeyJoinColumns) {
	    	metadataJoinColumns.add(processPrimaryKeyJoinColumn(primaryKeyJoinColumn.name(), primaryKeyJoinColumn.referencedColumnName(), primaryKeyJoinColumn.columnDefinition(), sourceTableName, targetTableName, descriptor));
	    }
        
	    return metadataJoinColumns;
    }

    /**
     * INTERNAL:
     * Process a @PrimaryKeyJoinColumns or @PrimaryKeyJoinColumn based on if
     * the descriptor metadata uses a composite primary key or not.
     * Returns a Vector of MetadataJoinColumn.
     */	
    protected ArrayList<MetadataJoinColumn> processPrimaryKeyJoinColumns(String sourceTableName, String targetTableName, Object element, MetadataDescriptor md) {
        ArrayList<MetadataJoinColumn> allPrimaryKeyJoinColumns = new ArrayList<MetadataJoinColumn>();
        AnnotatedElement annotatedElement = (AnnotatedElement) element;
        AnnotationsDescriptor descriptor = (AnnotationsDescriptor) md;
        
        if (descriptor.hasCompositePrimaryKey()) {
            // Look for a PrimaryKeyJoinColumns.
            PrimaryKeyJoinColumns primaryKeyJoinColumns = AnnotationsHelper.getAnnotation(PrimaryKeyJoinColumns.class, annotatedElement, descriptor);
            
            // The number of PrimaryKeyJoinColumns should equal the number of primary key fields.
            if (primaryKeyJoinColumns == null || primaryKeyJoinColumns.value().length != descriptor.getPrimaryKeyFields().size()) {
                getValidator().throwIncompletePrimaryKeyJoinColumnsSpecified(annotatedElement);
            }
            
            for (PrimaryKeyJoinColumn primaryKeyJoinColumn : primaryKeyJoinColumns.value()) {
                allPrimaryKeyJoinColumns.add(processPrimaryKeyJoinColumn(primaryKeyJoinColumn.name(), primaryKeyJoinColumn.referencedColumnName(), primaryKeyJoinColumn.columnDefinition(), sourceTableName, targetTableName, descriptor));
            }
        } else {
            // Look for a @PrimaryKeyJoinColumn.
            PrimaryKeyJoinColumn primaryKeyJoinColumn = AnnotationsHelper.getAnnotation(PrimaryKeyJoinColumn.class, annotatedElement, descriptor);
            
            if (primaryKeyJoinColumn == null) {
                if (descriptor.hasPrimaryKeyJoinColumns()) {
                    getValidator().throwExcessivePrimaryKeyJoinColumnsSpecified(annotatedElement);
                }
                
            	addJoinColumnDefault(allPrimaryKeyJoinColumns, sourceTableName, targetTableName, descriptor);   
            } else {
                allPrimaryKeyJoinColumns.add(processPrimaryKeyJoinColumn(primaryKeyJoinColumn.name(), primaryKeyJoinColumn.referencedColumnName(), primaryKeyJoinColumn.columnDefinition(), sourceTableName, targetTableName, descriptor));
            } 
        }
		
        return allPrimaryKeyJoinColumns;
    }
	
    /**
     * INTERNAL:
     * Process an array of @QueryHint.
     */	
    protected HashMap<String, String> processQueryHints(QueryHint[] queryHints) {
        HashMap<String, String> hm = new HashMap<String, String>();
		
        for (QueryHint queryHint : queryHints) {
            hm.put(queryHint.name(), queryHint.value());
        } 
		
        return hm;
    }
    
    /**
     * INTERNAL:
     * Process the related entities. That is, mappings and inheritance etc.
     */
    protected void processRelatedEntity(AnnotationsDescriptor descriptor) {
        // Process an inheritance subclass specifics.
        if (descriptor.isInheritanceSubclass()) {
            processInheritanceSubclass(descriptor);
        }
            
        // Process the relationship accessors.
        for (MetadataAccessor accessor : (Collection<MetadataAccessor>) descriptor.getRelationshipAccessors()) {
            processRelationshipAccessor(accessor);  
        }
    }
    
    /**
     * INTERNAL:
     * Process a @SecondaryTable and add it to descriptor. Method assumes that 
     * the class has been processed for a primary table and primary key.
     * WIP - If the @SecondaryTable does not define the pkJoinColumns(), we
     * could look for PrimaryKeyJoinColumns on the class itself. This is not
     * mandatory through.
     */
    protected void processSecondaryTable(SecondaryTable secondaryTable, AnnotationsDescriptor descriptor) {
        if (descriptor.ignoreTableAnnotations()) {
            // XML/Annotation merging. XML wins, ignore annotations.
            getLogger().logWarningMessage(AnnotationsLogger.IGNORE_SECONDARY_TABLE, descriptor, secondaryTable.name());
        } else {
            processSecondaryTable(
                secondaryTable.name(), 
            	secondaryTable.catalog(), 
            	secondaryTable.schema(),
            	secondaryTable.uniqueConstraints(),
            	secondaryTable.pkJoinColumns(),
            	descriptor);
        }
    }
    
    /**
     * INTERNAL:
     * Process a @SecondaryTables. If one isn't found, try a @SecondaryTable.
     */
    protected void processSecondaryTables(AnnotationsDescriptor descriptor) {
        // Look for a SecondaryTables annotation.
        SecondaryTables secondaryTables = AnnotationsHelper.getAnnotation(SecondaryTables.class, descriptor);
        if (secondaryTables != null) {
            for (SecondaryTable secondaryTable : secondaryTables.value()) {
                processSecondaryTable(secondaryTable, descriptor);
            }
        } else {
            // Look for a SecondaryTable annotation
            SecondaryTable secondaryTable = AnnotationsHelper.getAnnotation(SecondaryTable.class, descriptor);
            if (secondaryTable != null) {
                processSecondaryTable(secondaryTable, descriptor);
            }
        }
    }
    
    /**
     * INTERNAL:
     * Process a @SqlResultSetMapping. SqlResultSetMappings are stored on the
     * session.
     */
    protected void processSqlResultSetMapping(SqlResultSetMapping sqlResultSetMapping) {        
        // SqlResultSetMapping.name(), initialize a new SqlResultSetMapping.
        oracle.toplink.essentials.queryframework.SQLResultSetMapping mapping = new oracle.toplink.essentials.queryframework.SQLResultSetMapping(sqlResultSetMapping.name());
        
        // SqlResultSetMapping.entities()
        for (EntityResult entityResult : sqlResultSetMapping.entities()) {
            // EntityResult.entityClass()
            Class entityClass = entityResult.entityClass();
            oracle.toplink.essentials.queryframework.EntityResult eResult = new oracle.toplink.essentials.queryframework.EntityResult(entityClass.getName());
        
            // EntityResult.fields()
            for (FieldResult fieldResult : entityResult.fields()) {
                // Use the FieldResult.name() and FieldResult.column() values to 
                // create a new FieldResult and add it to the EntityResult.
                eResult.addFieldResult(new oracle.toplink.essentials.queryframework.FieldResult(fieldResult.name(), fieldResult.column()));
            }
        
            // EntityResult.discriminatorColumn()
            eResult.setDiscriminatorColumn(entityResult.discriminatorColumn());
        
            // Add the result to the SqlResultSetMapping.
            mapping.addResult(eResult);
        }
        
        // SqlResultSetMapping.columns()
        for (ColumnResult columnResult : sqlResultSetMapping.columns()) {
            // Use the ColumnResult.name() value to create a new ColumnResult 
            // and add it to the SqlResultSetMapping.
            mapping.addResult(new oracle.toplink.essentials.queryframework.ColumnResult(columnResult.name()));
        }
            
        m_session.getProject().addSQLResultSetMapping(mapping);
    }
    
    /**
     * INTERNAL:
     * Process a @SqlResultSetMappings.
     */
    protected void processSqlResultSetMappings(AnnotationsDescriptor descriptor) {
        processSqlResultSetMappings(descriptor.getJavaClass(), descriptor);
    }
    
    /**
     * INTERNAL:
     * Process a @SqlResultSetMappings.
     */
    protected void processSqlResultSetMappings(Class entityClass, AnnotationsDescriptor descriptor) {
        // Look for a SqlResultSetMappings annotation.
        SqlResultSetMappings sqlResultSetMappings = AnnotationsHelper.getAnnotation(SqlResultSetMappings.class, entityClass, descriptor);

        if (sqlResultSetMappings != null) {
            for (SqlResultSetMapping sqlResultSetMapping : sqlResultSetMappings.value()) {
                processSqlResultSetMapping(sqlResultSetMapping);
            }
        } else {
            // Look for a SqlResultSetMapping annotation.
            SqlResultSetMapping sqlResultSetMapping = AnnotationsHelper.getAnnotation(SqlResultSetMapping.class, entityClass, descriptor);
            
            if (sqlResultSetMapping != null) {
                processSqlResultSetMapping(sqlResultSetMapping);
            }
        }
    }
	
    /**
     * INTERNAL:
	 * Process a @Table.
	 */
	protected void processTable(MetadataDescriptor md) {
		// check to see if the XML processor used a default primary table
		// and update any existing database fields accordingly
		if (md.isDefaultPrimaryTableSet()) {
			adjustTableOnExistingFields(md);
		} else {
            if (md.ignoreTableAnnotations()) {
                // XML/Annotation merging. XML wins, ignore annotations.
                getLogger().logWarningMessage(AnnotationsLogger.IGNORE_TABLE, md);
            } else {
                Table table = AnnotationsHelper.getAnnotation(Table.class, md);
                if (table != null) {
                    // Process the table and add set it as primary.
                    processTable(table.name(), table.catalog(), table.schema(), table.uniqueConstraints(), md);
                } else {
                    processDefaultTable(md);
                }
            }
        }
    }
    
    /**
     * INTERNAL:
     * Process a @Temporal. The method may still be called if no @Temporal
     * has been specified but the accessor's reference class is a valid 
     * temporal type. 
     */
    protected void processTemporal(MetadataAccessor accessor, DirectToFieldMapping mapping) {
    	Temporal temporal = AnnotationsHelper.getAnnotation(Temporal.class, accessor);
        
        if (temporal == null) {
            getValidator().throwNoTemporalTypeSpecified(accessor.getJavaClass(), accessor.getAttributeName());
        } else {
            processTemporal(accessor, temporal.value().name(), mapping);
        }
    }
    
    /**
     * INTERNAL:
     * Process a @UniqueConstraint(s) for the given table.
     */
    protected void processUniqueConstraints(Object[] constraints, DatabaseTable table) {
        for (UniqueConstraint uniqueConstraint : (UniqueConstraint[]) constraints) {
            // UniqueConstraint.columnNames()
            String[] columnNames = uniqueConstraint.columnNames();
            table.addUniqueConstraint(columnNames);
        }
    }
    
    /** 
     * INTERNAL:
     * Set the classes for processing.
     */
    public void setClasses(Collection<Class> classes) {
        m_classes = classes;
    }
}
