/*******************************************************************************
 * 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:
 *     12/12/2008-1.1 Guy Pelletier 
 *       - 249860: Implement table per class inheritance support.
 ******************************************************************************/  
package org.eclipse.persistence.descriptors;

import java.io.*;
import java.util.*;

import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
import org.eclipse.persistence.mappings.ManyToManyMapping;
import org.eclipse.persistence.mappings.OneToManyMapping;
import org.eclipse.persistence.mappings.OneToOneMapping;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.ReadAllQuery;
import org.eclipse.persistence.queries.ReadObjectQuery;

/**
 * <p><b>Purpose</b>: Provides the functionality to support a TABLE_PER_CLASS
 * inheritance strategy. Resolves relational mappings and querying.
 */
public class TablePerClassPolicy extends InterfacePolicy implements Serializable, Cloneable {
    /**
     * Selection queries from read all mappings will be cached and re-used.
     * E.G Entity A has a 1-M to Entity D (who has a subclass Entity E). We will 
     * build a selection query to Entity E based on the selection query from the 
     * 1-M mapping from Entity A to Entity D.
     */
    protected HashMap<DatabaseMapping, DatabaseQuery> selectionQueriesForAllObjects;
    
    /**
     * INTERNAL:
     * Create a new policy.
     * Only descriptors involved in inheritance should have a policy.
     */
    public TablePerClassPolicy(ClassDescriptor descriptor) {
        setDescriptor(descriptor);
        selectionQueriesForAllObjects = new HashMap<DatabaseMapping, DatabaseQuery>();
    }
    
    /**
     * INTERNAL:
     */
    protected void addSelectionQuery(ForeignReferenceMapping cloneMapping, ForeignReferenceMapping sourceMapping, AbstractSession session) {
        // Set the new reference class
        cloneMapping.setReferenceClass(getDescriptor().getJavaClass());
        cloneMapping.setReferenceClassName(getDescriptor().getJavaClassName());
        
        // Force the selection criteria to be re-built.
        cloneMapping.setForceInitializationOfSelectionCriteria(true);
            
        // Now initialize the mapping
        cloneMapping.initialize(session);
            
        // The selection query should be initialized with all the right 
        // goodies now, cache it for quick retrieval.
        ObjectLevelReadQuery selectionQuery = (ObjectLevelReadQuery) cloneMapping.getSelectionQuery();
        selectionQuery.getExpressionBuilder().setQueryClassAndDescriptor(descriptor.getJavaClass(), descriptor);

        // By default its source mapping will be the cloned mapping, we
        // need to set the actual source mapping so that we can look it
        // back up correctly.
        selectionQuery.setSourceMapping(sourceMapping);
            
        // Cache the selection query for this source mapping.
        selectionQueriesForAllObjects.put(sourceMapping, selectionQuery);
    }
    
    /**
     * INTERNAL:
     */
    public boolean isTablePerClassPolicy() {
        return true;
    }
    
    /**
     * INTERNAL:
     * This method is called from individual mappings during their 
     * initialization. If the mapping is to a class within a TABLE_PER_CLASS
     * inheritance hierarchy, what this method will do is prepare a selection 
     * query to execute for every child descriptor in the hierarchy.
     * 
     * The selection queries are created by cloning the source mapping,
     * updating the necessary database fields on the mapping and then 
     * initializing the mapping to create the internal selection query. 
     * This query is then cached where needed using the source mapping's
     * selection query name as the key.
     * 
     * @see selectAllObjects(ReadAllQuery)
     * @see selectOneObject(ReadObjectQuery)
     */
    public void prepareChildrenSelectionQuery(DatabaseMapping sourceMapping, AbstractSession session) {
        // From collection mappings we must execute a query on every
        // subclass table to build our results. Tell all children 
        // descriptors to prepare their selection queries.
        for (ClassDescriptor childDescriptor : (Vector<ClassDescriptor>) getChildDescriptors()) {
            childDescriptor.getTablePerClassPolicy().prepareSelectionQuery(sourceMapping, session);
        }
    }
    
    /**
     * INTERNAL:
     */
    protected void prepareManyToManySelectionQuery(ManyToManyMapping sourceMapping, AbstractSession session) {
        // Clone the mapping because in reality that is what we have, that 
        // is, a M-M mapping to each class of the hierarchy.
        ManyToManyMapping manyToMany = (ManyToManyMapping) sourceMapping.clone();
        
        // Update the foreign key fields on the mapping. Basically, take the
        // table name off and let the descriptor figure it out.
        for (DatabaseField keyField : manyToMany.getTargetKeyFields()) {
            keyField.setTable(new DatabaseTable());
        }
        
        addSelectionQuery(manyToMany, sourceMapping, session);
    }
    
    /**
     * INTERNAL:
     */
    protected void prepareOneToManySelectionQuery(OneToManyMapping sourceMapping, AbstractSession session) {
        // Clone the mapping because in reality that is what we have, that 
        // is, a 1-M mapping to each class of the hierarchy.
        OneToManyMapping oneToMany = (OneToManyMapping) sourceMapping.clone();
            
        // Update the foreign key fields on the mapping. Basically, take the
        // table name off and let the descriptor figure it out.
        Vector<DatabaseField> targetForeignKeyFields = new Vector<DatabaseField>();
        for (DatabaseField fkField : oneToMany.getTargetForeignKeysToSourceKeys().keySet()) {
            targetForeignKeyFields.add(new DatabaseField(fkField.getName()));
        }
                    
        // Update our foreign key fields and clear the key maps.
        oneToMany.setTargetForeignKeyFields(targetForeignKeyFields);
        oneToMany.getTargetForeignKeysToSourceKeys().clear();
        oneToMany.getSourceKeysToTargetForeignKeys().clear();
        
        addSelectionQuery(oneToMany, sourceMapping, session);
    }
    
    /**
     * INTERNAL:
     */
    protected void prepareOneToOneSelectionQuery(OneToOneMapping sourceMapping, AbstractSession session) {
        // Clone the mapping because in reality that is what we have, that 
        // is, a 1-1 mapping to each class of the hierarchy.
        OneToOneMapping oneToOne = (OneToOneMapping) sourceMapping.clone();
            
        // Update the target keys to have an empty table (descriptor will figure it out)
        for (DatabaseField targetField : oneToOne.getTargetToSourceKeyFields().keySet()) {
            targetField.setTable(new DatabaseTable());
        }
        
        addSelectionQuery(oneToOne, sourceMapping, session);
    }
    
    /**
     * INTERNAL:
     * The selection queries are created by cloning the source mapping,
     * updating the necessary database fields on the mapping and then 
     * initializing the mapping to create the internal selection query. 
     * This query is then cached where needed using the source mapping 
     * as the key. A prepare is performed for each child of the hierarchy.
     */
    protected void prepareSelectionQuery(DatabaseMapping sourceMapping, AbstractSession session) {
        // Recurse through our child descriptors to set up the selection query
        // before execution.
        for (ClassDescriptor childDescriptor : (Vector<ClassDescriptor>) getChildDescriptors()) {
            childDescriptor.getTablePerClassPolicy().prepareSelectionQuery(sourceMapping, session);
        }
        
        if (sourceMapping.isOneToManyMapping()) {
            prepareOneToManySelectionQuery((OneToManyMapping) sourceMapping, session);
        } else if (sourceMapping.isManyToManyMapping()) {
            prepareManyToManySelectionQuery((ManyToManyMapping) sourceMapping, session);
        } else if (sourceMapping.isOneToOneMapping()) {
            prepareOneToOneSelectionQuery((OneToOneMapping) sourceMapping, session);
        }
        
        // No support for other mappings at this point.
    }
    
    /**
     * INTERNAL:
     * Select all objects for a concrete descriptor.
     */
    @Override
    protected Object selectAllObjects(ReadAllQuery query) { 
        // If we came from a source mapping the execute the selection query
        // we prepared from it.
        if (selectionQueriesForAllObjects.containsKey(query.getSourceMapping())) {
            return query.getExecutionSession().executeQuery(selectionQueriesForAllObjects.get(query.getSourceMapping()), query.getTranslationRow());  
        } else {
            return super.selectAllObjects(query);
        }
    }
    
    /**
     * INTERNAL:
     * Select one object of any concrete subclass.
     */
    @Override
    protected Object selectOneObject(ReadObjectQuery query) throws DescriptorException {
        // If we came from a source mapping the execute the selection query
        // we prepared from it.
        if (selectionQueriesForAllObjects.containsKey(query.getSourceMapping())) {
            return query.getExecutionSession().executeQuery(selectionQueriesForAllObjects.get(query.getSourceMapping()), query.getTranslationRow());  
        } else {
            // Assuming we're doing a find by primary key ...
            // We have to update the translation row to be to the correct field.
            AbstractRecord translationRow = (AbstractRecord) query.getTranslationRow().clone();
            Vector allFields = new Vector();
            
            for (DatabaseField field : (Vector<DatabaseField>) translationRow.getFields()) {
                // Remove the table and let the descriptor figure it out.
                allFields.add(new DatabaseField(field.getName()));
            }
            
            translationRow.getFields().clear();
            translationRow.getFields().addAll(allFields);
            return query.getSession().executeQuery(getDescriptor().getQueryManager().getReadObjectQuery(), translationRow);
        }
    }
}
