/*******************************************************************************
 * Copyright (c) 1998, 2008 Oracle. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 *     05/16/2008-1.0M8 Guy Pelletier 
 *       - 218084: Implement metadata merging functionality between mapping files
 *     09/23/2008-1.1 Guy Pelletier 
 *       - 241651: JPA 2.0 Access Type support
 *     10/01/2008-1.1 Guy Pelletier 
 *       - 249329: To remain JPA 1.0 compliant, any new JPA 2.0 annotations should be referenced by name
 ******************************************************************************/  
package org.eclipse.persistence.internal.jpa.metadata.accessors.mappings;

import java.lang.annotation.Annotation;
import java.util.HashMap;

import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.helper.DatabaseField;

import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAccessibleObject;

import org.eclipse.persistence.internal.jpa.metadata.columns.AttributeOverrideMetadata;

import org.eclipse.persistence.mappings.AggregateObjectMapping;
import org.eclipse.persistence.mappings.DatabaseMapping;

/**
 * An embedded id relationship accessor.
 * 
 * @author Guy Pelletier
 * @since TopLink EJB 3.0 Reference Implementation
 */
public class EmbeddedIdAccessor extends EmbeddedAccessor {
    // We store map of fields that are the primary key and add them only at the
    // end of processing since they may change when processing attribute 
    // overrides. They are mapped by attribute name.
    protected HashMap<String, DatabaseField> m_idFields = new HashMap<String, DatabaseField>();

    /**
     * INTERNAL:
     * Default constructor.
     */
    public EmbeddedIdAccessor() {
        super("<embedded-id>");
    }
    
    /**
     * INTERNAL:
     */
    public EmbeddedIdAccessor(Annotation embeddedId, MetadataAccessibleObject accessibleObject, ClassAccessor classAccessor) {
        super(embeddedId, accessibleObject, classAccessor);
    }
    
    /**
     * INTERNAL:
     * Process an EmbeddedId metadata.
     */    
    @Override
    public void process() {
        // Check if we already processed an EmbeddedId for this entity.
        if (getOwningDescriptor().hasEmbeddedIdAttribute()) {
            throw ValidationException.multipleEmbeddedIdAnnotationsFound(getJavaClass(), getAttributeName(), getOwningDescriptor().getEmbeddedIdAttributeName());
        } 
        
        // Check if we already processed an Id or IdClass.
        if (getOwningDescriptor().hasPrimaryKeyFields()) {
            throw ValidationException.embeddedIdAndIdAnnotationFound(getJavaClass(), getAttributeName(), getOwningDescriptor().getIdAttributeName());
        }
        
        // Now process the embeddable and our embedded metadata. This must be
        // done now and before the calls below.
        super.process();
        
        // Set the PK class.
        getOwningDescriptor().setPKClass(getReferenceClass());
            
        // Store the embeddedId attribute name.
        getOwningDescriptor().setEmbeddedIdAttributeName(getAttributeName());
        
        // After processing the embeddable class, we need to gather our 
        // primary keys fields that we will eventually set on the owning 
        // descriptor metadata.
        if (getReferenceDescriptor().getMappings().isEmpty()) {
            throw ValidationException.embeddedIdHasNoAttributes(getDescriptor().getJavaClass(), getReferenceDescriptor().getJavaClass(), getReferenceDescriptor().getClassAccessor().getAccessType());
        } else {
            // Go through all our mappings, the fields from those mappings will
            // make up the composite primary key.
            for (DatabaseMapping mapping : getReferenceDescriptor().getMappings()) {
                if (mapping.isDirectToFieldMapping()) {
                    if (! m_idFields.containsKey(mapping.getAttributeName())) {
                        // It may be in our id fields map already if an attribute
                        // override was specified on the embedded mapping.
                        m_idFields.put(mapping.getAttributeName(), mapping.getField());
                    }
                } else {
                    // EmbeddedId is solely a JPA feature, so we will not
                    // allow the expansion of attributes for those types of
                    // Embeddable classes beyond basics as defined in the spec.
                    throw ValidationException.invalidMappingForEmbeddedId(getAttributeName(), getJavaClass(), mapping.getAttributeName(), getReferenceDescriptor().getJavaClass());
                }
            }
        
            // Add all the fields from the embeddable as primary keys on the 
            // owning metadata descriptor.
            for (DatabaseField field : m_idFields.values()) {
                if (!getOwningDescriptor().getPrimaryKeyFieldNames().contains(field.getName())) {
                    // Set a table if one is not specified. Because embeddables 
                    // can be re-used we must deal with clones and not change 
                    // the original fields.
                    DatabaseField clone = (DatabaseField) field.clone();
                    if (clone.getTableName().equals("")) {
                        clone.setTable(getOwningDescriptor().getPrimaryTable());
                    }
                    
                    getOwningDescriptor().addPrimaryKeyField(clone);
                }
            }
        }
    }
    
    /**
     * INTERNAL:
     * Process an attribute override for an embedded object, that is, an 
     * aggregate object mapping in EclipseLink.
     */
    @Override
    protected void processAttributeOverride(AggregateObjectMapping mapping, AttributeOverrideMetadata attributeOverride) {
        super.processAttributeOverride(mapping, attributeOverride);
        
        // Update our primary key field with the attribute override field.
        // The super class will ensure the correct field is on the metadata
        // column.
        m_idFields.put(attributeOverride.getName(), attributeOverride.getColumn().getDatabaseField());
    }
}
