/*******************************************************************************
 * 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
 ******************************************************************************/  
package org.eclipse.persistence.queries;

import java.util.*;
import java.sql.*;
import org.eclipse.persistence.internal.databaseaccess.*;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.internal.descriptors.ObjectBuilder;
import org.eclipse.persistence.internal.queries.*;
import org.eclipse.persistence.internal.expressions.*;
import org.eclipse.persistence.internal.sessions.remote.*;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.expressions.*;
import org.eclipse.persistence.sessions.remote.*;
import org.eclipse.persistence.tools.profiler.QueryMonitor;

/**
 * <p><b>Purpose</b>:
 * Concrete class for all read queries involving a collection of objects.
 * <p>
 * <p><b>Responsibilities</b>:
 * Return a container of the objects generated by the query.
 * Implements the inheritance feature when dealing with abstract descriptors
 *
 * @author Yvon Lavoie
 * @since TOPLink/Java 1.0
 */
public class ReadAllQuery extends ObjectLevelReadQuery {

    /** Used for ordering support. */
    protected Vector orderByExpressions;

    /** Used for query optimization. */
    protected Vector batchReadAttributeExpressions;
    /** PERF: Used internally for batch reading. */
    protected transient Map batchReadMappingQueries;
    /** PERF: Cache the local batch read attribute names. */
    protected List batchReadAttributes;

    /** Used for collection and stream support. */
    protected ContainerPolicy containerPolicy;

    /** Used for Oracle HierarchicalQuery support */
    protected Expression startWithExpression;
    protected Expression connectByExpression;
    protected Vector orderSiblingsByExpressions;

    /**
     * PUBLIC:
     * Return a new read all query.
     * A reference class must be specified before execution.
     * It is better to provide the class and expression builder on construction to ensure a single expression builder is used.
     * If no selection criteria is specified this will read all objects of the class from the database.
     */
    public ReadAllQuery() {
        super();
        this.useCollectionClass(ClassConstants.Vector_class);
    }

    /**
     * PUBLIC:
     * Return a new read all query.
     * It is better to provide the class and expression builder on construction to esnure a single expression builder is used.
     * If no selection criteria is specified this will read all objects of the class from the database.
     */
    public ReadAllQuery(Class classToRead) {
        this();
        this.referenceClass = classToRead;
    }

    /**
     * PUBLIC:
     * Return a new read all query for the class and the selection criteria.
     */
    public ReadAllQuery(Class classToRead, Expression selectionCriteria) {
        this();
        this.referenceClass = classToRead;
        setSelectionCriteria(selectionCriteria);
    }

    /**
     * PUBLIC:
     * Return a new read all query for the class.
     * The expression builder must be used for all associated expressions used with the query.
     */
    public ReadAllQuery(Class classToRead, ExpressionBuilder builder) {
        this();
        this.defaultBuilder = builder;
        this.referenceClass = classToRead;
    }

    /**
     * PUBLIC:
     * Return a new read all query.
     * The call represents a database interaction such as SQL, Stored Procedure.
     */
    public ReadAllQuery(Class classToRead, Call call) {
        this();
        this.referenceClass = classToRead;
        setCall(call);
    }

    /**
     * PUBLIC:
     * Return a query by example query to find all objects matching the attributes of the example object.
     */
    public ReadAllQuery(Object exampleObject, QueryByExamplePolicy policy) {
        this();
        setExampleObject(exampleObject);
        setQueryByExamplePolicy(policy);
    }

    /**
     * PUBLIC:
     * The expression builder should be provide on creation to ensure only one is used.
     */
    public ReadAllQuery(ExpressionBuilder builder) {
        this();
        this.defaultBuilder = builder;
    }

    /**
     * PUBLIC:
     * Create a read all query with the database call.
     */
    public ReadAllQuery(Call call) {
        this();
        setCall(call);
    }

    /**
     * PUBLIC:
     * Order the query results by the object's attribute or query key name.
     */
    public void addAscendingOrdering(String queryKeyName) {
        addOrdering(getExpressionBuilder().get(queryKeyName).ascending());
    }

    /**
     * PUBLIC:
     * Specify the foreign-reference mapped attribute to be optimized in this query.
     * The query will execute normally, however when any of the batched parts is accessed,
     * the parts will all be read in a single query,
     * this allows all of the data required for the parts to be read in a single query instead of (n) queries.
     * This should be used when the application knows that it requires the part for all of the objects being read.
     * This can be used for one-to-one, one-to-many, many-to-many and direct collection mappings.
     *
     * The use of the expression allows for nested batch reading to be expressed.
     * <p>Example: query.addBatchReadAttribute("phoneNumbers")
     *
     * @see #addBatchReadAttribute(Expression)
     * @see ObjectLevelReadQuery#addJoinedAttribute(String)
     */
    public void addBatchReadAttribute(String attributeName) {
        if (! getQueryMechanism().isExpressionQueryMechanism()){
            throw QueryException.batchReadingNotSupported(this);
        }
        getBatchReadAttributeExpressions().add(getExpressionBuilder().get(attributeName));
    }

    /**
     * PUBLIC:
     * Specify the foreign-reference mapped attribute to be optimized in this query.
     * The query will execute normally, however when any of the batched parts is accessed,
     * the parts will all be read in a single query,
     * this allows all of the data required for the parts to be read in a single query instead of (n) queries.
     * This should be used when the application knows that it requires the part for all of the objects being read.
     * This can be used for one-to-one, one-to-many, many-to-many and direct collection mappings.
     *
     * The use of the expression allows for nested batch reading to be expressed.
     * <p>Example: query.addBatchReadAttribute(query.getExpressionBuilder().get("policies").get("claims"))
     *
     * @see ObjectLevelReadQuery#addJoinedAttribute(String)
     */
    public void addBatchReadAttribute(Expression attributeExpression) {
        getBatchReadAttributeExpressions().add(attributeExpression);
    }

    /**
     * PUBLIC:
     * Order the query results by the object's attribute or query key name.
     */
    public void addDescendingOrdering(String queryKeyName) {
        addOrdering(getExpressionBuilder().get(queryKeyName).descending());
    }

    /**
     * PUBLIC:
     * Add the ordering expression.  This allows for ordering across relationships or functions.
     * Example: readAllQuery.addOrdering(expBuilder.get("address").get("city").toUpperCase().descending())
     */
    public void addOrdering(Expression orderingExpression) {
        getOrderByExpressions().addElement(orderingExpression);
        //Bug2804042 Must un-prepare if prepared as the SQL may change.
        setIsPrepared(false);
    }

    /**
     * INTERNAL:
     * <P> This method is called by the object builder when building an original.
     * It will cause the original to be cached in the query results if the query
     * is set to do so.
     */
    public void cacheResult(Object unwrappedOriginal) {
        Collection container = (Collection)getTemporaryCachedQueryResults();
        if (container == null) {
            container = (Collection)getContainerPolicy().containerInstance();
            setTemporaryCachedQueryResults(container);
        }
        getContainerPolicy().addInto(unwrappedOriginal, container, getSession());
    }

    /**
     * INTERNAL:
     * The cache check is done before the prepare as a hit will not require the work to be done.
     */
    protected Object checkEarlyReturnImpl(AbstractSession session, AbstractRecord translationRow) {
        // Check for in-memory only query.
        if (shouldCheckCacheOnly()) {
            // assert !isReportQuery();
            if (shouldUseWrapperPolicy()) {
                getContainerPolicy().setElementDescriptor(getDescriptor());
            }

            // PERF: Fixed to not query each unit of work cache (is not conforming),
            // avoid hashtable and primary key indexing.
            // At some point we may need to support some kind of in-memory with conforming option,
            // but we do not currently allow this.
            AbstractSession rootSession = session;
            while (rootSession.isUnitOfWork()) {
                rootSession = ((UnitOfWorkImpl)rootSession).getParent();
            }
            Vector allCachedVector = rootSession.getIdentityMapAccessor().getAllFromIdentityMap(getSelectionCriteria(), getReferenceClass(), translationRow, getInMemoryQueryIndirectionPolicyState(), false);

            // Must ensure that all of the objects returned are correctly registered in the unit of work.
            if (session.isUnitOfWork()) {
                allCachedVector = ((UnitOfWorkImpl)session).registerAllObjects(allCachedVector);
            }

            return getContainerPolicy().buildContainerFromVector(allCachedVector, session);
        } else {
            return null;
        }
    }

    /**
     * INTERNAL:
     * Check to see if a custom query should be used for this query.
     * This is done before the query is copied and prepared/executed.
     * null means there is none.
     */
    protected DatabaseQuery checkForCustomQuery(AbstractSession session, AbstractRecord translationRow) {
        checkDescriptor(session);

        // Check if user defined a custom query.
        if (isCustomQueryUsed() == null) {
            setIsCustomQueryUsed((!isUserDefined()) && isExpressionQuery() && (getSelectionCriteria() == null) && (!hasOrderByExpressions()) && (getDescriptor().getQueryManager().hasReadAllQuery()));
        }
        if (isCustomQueryUsed().booleanValue()) {
            return getDescriptor().getQueryManager().getReadAllQuery();
        } else {
            return null;
        }
    }

    /**
     * INTERNAL:
     * Clone the query.
     */
    public Object clone() {
        ReadAllQuery cloneQuery = (ReadAllQuery)super.clone();

        // Don't use setters as that will trigger unprepare
        if (hasOrderByExpressions()) {
            cloneQuery.orderByExpressions = (Vector)getOrderByExpressions().clone();
        }
        cloneQuery.containerPolicy = getContainerPolicy().clone(cloneQuery);

        return cloneQuery;
    }

    /**
     * INTERNAL:
     * Conform the result if specified.
     */
    protected Object conformResult(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord arguments, boolean buildDirectlyFromRows) {
        if (getSelectionCriteria() != null) {
            ExpressionBuilder builder = getSelectionCriteria().getBuilder();
            builder.setSession(unitOfWork.getRootSession(null));
            builder.setQueryClass(getReferenceClass());
        }
        
        // If the query is redirected then the collection returned might no longer
        // correspond to the original container policy.  CR#2342-S.M.
        ContainerPolicy cp;
        if (getRedirector() != null) {
            cp = ContainerPolicy.buildPolicyFor(result.getClass());
        } else {
            cp = getContainerPolicy();
        }

        // This code is now a great deal different...  For one, registration is done
        // as part of conforming.  Also, this should only be called if one actually
        // is conforming.
        // First scan the UnitOfWork for conforming instances.
        // This will walk through the entire cache of registered objects.
        // Let p be objects from result not in the cache.
        // Let c be objects from cache.
        // Presently p intersect c = empty set, but later p subset c.
        // By checking cache now doesConform will be called p fewer times.
        Map indexedInterimResult = unitOfWork.scanForConformingInstances(getSelectionCriteria(), getReferenceClass(), arguments, this);
        
        Cursor cursor = null;
        // In the case of cursors just conform/register the initially read collection.
        if (cp.isCursorPolicy()) {
            cursor = (Cursor)result;
            cp = ContainerPolicy.buildPolicyFor(ClassConstants.Vector_class);
            // In nested UnitOfWork session might have been session of the parent.
            cursor.setSession(unitOfWork);
            result = cursor.getObjectCollection();
            // for later incremental conforming...
            cursor.setInitiallyConformingIndex(indexedInterimResult);
            cursor.setSelectionCriteriaClone(getSelectionCriteria());
            cursor.setTranslationRow(arguments);
        }

        // Now conform the result from the database.
        // Remove any deleted or changed objects that no longer conform.
        // Deletes will only work for simple queries, queries with or's or anyof's may not return
        // correct results when untriggered indirection is in the model.		
        Vector fromDatabase = null;

        // When building directly from rows, one of the performance benefits
        // is that we no longer have to wrap and then unwrap the originals.
        // result is just a vector, not a container of wrapped originals.
        if (buildDirectlyFromRows) {
            Vector rows = (Vector)result;
            fromDatabase = new Vector(rows.size());
            for (int i = 0; i < rows.size(); i++) {
                Object object = rows.elementAt(i);
                // null is placed in the row collection for 1-m joining to filter duplicate rows.
                if (object != null) {
                    Object clone = conformIndividualResult(object, unitOfWork, arguments, getSelectionCriteria(), indexedInterimResult, buildDirectlyFromRows);
                    if (clone != null) {
                        fromDatabase.addElement(clone);
                    }
                }
            }
        } else {
            fromDatabase = new Vector(cp.sizeFor(result));
            AbstractSession sessionToUse = unitOfWork.getParent();
            for (Object iter = cp.iteratorFor(result); cp.hasNext(iter);) {
                Object object = cp.next(iter, sessionToUse);
                Object clone = conformIndividualResult(object, unitOfWork, arguments, getSelectionCriteria(), indexedInterimResult, buildDirectlyFromRows);
                if (clone != null) {
                    fromDatabase.addElement(clone);
                }
            }
        }

        // Now add the unwrapped conforming instances into an appropriate container.  
        // Wrapping is done automatically.
        // Make sure a vector of exactly the right size is returned.
        Object conformedResult = cp.containerInstance(indexedInterimResult.size() + fromDatabase.size());
        Object eachClone;
        for (Iterator enumtr = indexedInterimResult.values().iterator(); enumtr.hasNext();) {
            eachClone = enumtr.next();
            cp.addInto(eachClone, conformedResult, unitOfWork);
        }
        for (Enumeration enumtr = fromDatabase.elements(); enumtr.hasMoreElements();) {
            eachClone = enumtr.nextElement();
            cp.addInto(eachClone, conformedResult, unitOfWork);
        }

        if (cursor != null) {
            cursor.setObjectCollection((Vector)conformedResult);

            // For nested UOW must copy all in object collection to
            // initiallyConformingIndex, as some of these could have been from 
            // the parent UnitOfWork.
            if (unitOfWork.isNestedUnitOfWork()) {
                for (Enumeration enumtr = cursor.getObjectCollection().elements();
                         enumtr.hasMoreElements();) {
                    Object clone = enumtr.nextElement();
                    indexedInterimResult.put(clone, clone);
                }
            }
            return cursor;
        } else {
            return conformedResult;
        }
    }

    /**
     * INTERNAL:
     * Execute the query. If there are cached results return those.
     * This must override the super to support result caching.
     *
     * @param session - the session in which the receiver will be executed.
     * @return An object or vector, the result of executing the query.
     * @exception DatabaseException - an error has occurred on the database
     */
    public Object execute(AbstractSession session, AbstractRecord row) throws DatabaseException {        
        if (shouldCacheQueryResults()) {
            if (getContainerPolicy().overridesRead()) {
                throw QueryException.cannotCacheCursorResultsOnQuery(this);
            }
            if (shouldConformResultsInUnitOfWork()) {
                throw QueryException.cannotConformAndCacheQueryResults(this);
            }
            if (isPrepared()) {// only prepared queries can have cached results.
                Object queryResults = getQueryResults(session, row, true);
                if (queryResults != null) {
                    if (QueryMonitor.shouldMonitor()) {
                        QueryMonitor.incrementReadAllHits(this);
                    }                    
                    // bug6138532 - check for "cached no results" (InvalidObject singleton) in query 
                    // results, and return an empty container instance as configured
                    if (queryResults == InvalidObject.instance) {
                        return getContainerPolicy().containerInstance();
                    }
                    Collection results = (Collection)queryResults;
                    if (session.isUnitOfWork()) {
                        Collection resultCollection = (Collection)getContainerPolicy().containerInstance();
                        Object iterator = getContainerPolicy().iteratorFor(results);
                        while (getContainerPolicy().hasNext(iterator)) {
                            Object result = ((UnitOfWorkImpl)session).registerExistingObject(getContainerPolicy().next(iterator, session));
                            resultCollection.add(result);
                        }
                        return resultCollection;
                    }
                    return results;
                }
            }
        }
        if (QueryMonitor.shouldMonitor()) {
            QueryMonitor.incrementReadAllMisses(this);
        }
        return super.execute(session, row);
    }

    /**
     * INTERNAL:
     * Execute the query.
     * Get the rows and build the object from the rows.
     * @exception  DatabaseException - an error has occurred on the database
     * @return java.lang.Object collection of objects resulting from execution of query.
     */
    protected Object executeObjectLevelReadQuery() throws DatabaseException {
        Object result = null;
        if (getContainerPolicy().overridesRead()) {
            return getContainerPolicy().execute();
        }

        if (getDescriptor().isDescriptorForInterface()) {
            Object returnValue = getDescriptor().getInterfacePolicy().selectAllObjectsUsingMultipleTableSubclassRead(this);
            setExecutionTime(System.currentTimeMillis());
            return returnValue;
        }

        Vector rows = getQueryMechanism().selectAllRows();
        setExecutionTime(System.currentTimeMillis());
        // If using 1-m joins, must set all rows.
        if (hasJoining() && getJoinedAttributeManager().isToManyJoin()) {
            getJoinedAttributeManager().setDataResults(rows, getSession());
        }

        if (getSession().isUnitOfWork()) {
            result = registerResultInUnitOfWork(rows, (UnitOfWorkImpl)getSession(), getTranslationRow(), true);// 
        } else {
            result = getQueryMechanism().buildObjectsFromRows(rows);
        }

        if (shouldIncludeData()) {
            ComplexQueryResult complexResult = new ComplexQueryResult();
            complexResult.setResult(result);
            complexResult.setData(rows);
            return complexResult;
        }

        return result;
    }

    /**
     * INTERNAL:
     * Execute the query building the objects directly from the database result-set.
     * @exception  DatabaseException - an error has occurred on the database
     * @return object - the first object found or null if none.
     */
    protected Object executeObjectLevelReadQueryFromResultSet() throws DatabaseException {
        UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)getSession();
        DatabaseAccessor accessor = (DatabaseAccessor)unitOfWork.getAccessor();
        DatabasePlatform platform = accessor.getPlatform();
        DatabaseCall call = (DatabaseCall)getCall().clone();
        call.setQuery(this);
        call.translate(this.translationRow, null, unitOfWork);
        Statement statement = null;
        ResultSet resultSet = null;
        boolean exceptionOccured = false;
        try {
            accessor.incrementCallCount(unitOfWork);
            statement = call.prepareStatement(accessor, this.translationRow, unitOfWork);
            resultSet = accessor.executeSelect(call, statement, unitOfWork);
            ResultSetMetaData metaData = resultSet.getMetaData();
            Vector results = new Vector();
            ObjectBuilder builder = this.descriptor.getObjectBuilder();
            while (resultSet.next()) {
                results.add(builder.buildWorkingCopyCloneFromResultSet(this, this.joinedAttributeManager, resultSet, unitOfWork, accessor, metaData, platform));
            }
            return results;
        } catch (SQLException exception) {
            exceptionOccured = true;
            DatabaseException commException = accessor.processExceptionForCommError(session, exception, call);
            if (commException != null) throw commException;
            throw DatabaseException.sqlException(exception, call, accessor, unitOfWork, false);
        } finally {
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
                if (statement != null) {
                    accessor.releaseStatement(statement, call.getSQLString(), call, unitOfWork);
                }
            } catch (SQLException exception) {
                if (!exceptionOccured) {
                    //in the case of an external connection pool the connection may be null after the statement release
                    // if it is null we will be unable to check the connection for a comm error and
                    //therefore must return as if it was not a comm error.
                    DatabaseException commException = accessor.processExceptionForCommError(session, exception, call);
                    if (commException != null) throw commException;
                    throw DatabaseException.sqlException(exception, call, accessor, session, false);
                }
            }
        }
    }
    
    /**
     * INTERNAL:
     * Extract the correct query result from the transporter.
     */
    public Object extractRemoteResult(Transporter transporter) {
        return ((RemoteSession)getSession()).getObjectsCorrespondingToAll(transporter.getObject(), transporter.getObjectDescriptors(), new IdentityHashMap(), this, getContainerPolicy());
    }

    /**
     * INTERNAL:
     * Return all attributes specified for batch reading.
     */
    public Vector getBatchReadAttributeExpressions() {
        if (batchReadAttributeExpressions == null) {
            batchReadAttributeExpressions = NonSynchronizedVector.newInstance();
        }        
        return batchReadAttributeExpressions;
    }

    /**
     * INTERNAL:
     * Return the query's container policy.
     * @return org.eclipse.persistence.internal.queries.ContainerPolicy
     */
    public ContainerPolicy getContainerPolicy() {
        return containerPolicy;
    }

    /**
     * INTERNAL:
     * Returns the specific default redirector for this query type.  There are numerous default query redirectors.
     * See ClassDescriptor for their types.
     */
    protected QueryRedirector getDefaultRedirector(){
        return descriptor.getDefaultReadAllQueryRedirector();
    }

    /**
     * PUBLIC:
     * @return Expression - the start with expression used to generated the hierarchical query clause in
     * Oracle
     */
    public Expression getStartWithExpression() {
        return startWithExpression;
    }

    /**
     * PUBLIC:
     * @return Expression - the connect by expression used to generate the hierarchical query caluse in Oracle
     */
    public Expression getConnectByExpression() {
        return connectByExpression;
    }

    /**
     * PUBLIC:
     * @return Vector - the ordering expressions used to generate the hierarchical query clause in Oracle
     */
    public Vector getOrderSiblingsByExpressions() {
        return orderSiblingsByExpressions;
    }

    /**
     * INTERNAL:
     * Return the order expressions for the query.
     */
    public Vector getOrderByExpressions() {
        if (orderByExpressions == null) {
            orderByExpressions = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance();
        }
        return orderByExpressions;
    }

    /**
     * INTERNAL:
     * The order bys are lazy initialized to conserve space.
     */
    public boolean hasOrderByExpressions() {
        return orderByExpressions != null;
    }

    /**
     * INTERNAL:
     * Verify that we have hierarchical query expressions
     */
    public boolean hasHierarchicalExpressions() {
        return ((startWithExpression != null) || (connectByExpression != null) || (orderSiblingsByExpressions != null));
    }

    /**
     * INTERNAL:
     * Return true is this query has batching
     */
    public boolean hasBatchReadAttributes() {
        return (batchReadAttributeExpressions != null) && (!batchReadAttributeExpressions.isEmpty());
    }

    /**
     * INTERNAL:
     * Return if the attribute is specified for batch reading.
     */
    public boolean isAttributeBatchRead(String attributeName) {
        if (!hasBatchReadAttributes()) {
            return false;
        }
        Vector batchReadAttributeExpressions = getBatchReadAttributeExpressions();
        int size = batchReadAttributeExpressions.size();
        for (int index = 0; index < size; index++) {
            QueryKeyExpression expression = (QueryKeyExpression)batchReadAttributeExpressions.get(index);
            while (!expression.getBaseExpression().isExpressionBuilder()) {
                expression = (QueryKeyExpression)expression.getBaseExpression();
            }
            if (expression.getName().equals(attributeName)) {
                return true;
            }
        }
        return false;
    }
        
    /**
     * INTERNAL:
     * Return if the attribute is specified for batch reading.
     */
    public boolean isAttributeBatchRead(ClassDescriptor mappingDescriptor, String attributeName) {
        if (!hasBatchReadAttributes()) {
            return false;
        }
        // Since aggregates share the same query as their parent, must avoid the aggregate thinking
        // the parents mappings is for it, (queries only share if the aggregate was not joined).
        if (mappingDescriptor.isAggregateDescriptor() && (mappingDescriptor != getDescriptor())) {
            return false;
        }
        if (this.batchReadAttributes != null) {
            return this.batchReadAttributes.contains(attributeName);
        }
        return isAttributeBatchRead(attributeName);
    }
    
    /**
     * INTERNAL:
     * Return the batch read mapping queries, used to optimize batch reading, only compute the nested queries once.
     */
    public Map getBatchReadMappingQueries() {
        return batchReadMappingQueries;
    }

    /**
     * INTERNAL:
     * Set the batch read mapping queries, used to optimize batch reading, only compute the nested queries once.
     */
    protected void setBatchReadMappingQueries(Map batchReadMappingQueries) {
        this.batchReadMappingQueries = batchReadMappingQueries;
    }
    
    /**
     * INTERNAL:
     * Return true if the query uses default properties.
     * This is used to determine if this query is cacheable.
     * i.e. does not use any properties that may conflict with another query
     * with the same JPQL or selection criteria.
     */
    public boolean isDefaultPropertiesQuery() {
        return super.isDefaultPropertiesQuery()
            && (!hasBatchReadAttributes())
            && (!hasHierarchicalExpressions())
            && (!getContainerPolicy().isCursorPolicy());
    }
    
    /**
     * INTERNAL:
     * Return if the query is equal to the other.
     * This is used to allow dynamic expression query SQL to be cached.
     */
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (!super.equals(object)) {
            return false;
        }
        ReadAllQuery query = (ReadAllQuery) object;
        if (!getContainerPolicy().equals(query.getContainerPolicy())) {
            return false;
        }
        if (hasOrderByExpressions()) {
            if (!query.hasOrderByExpressions()) {
                return false;
            }
            List orderBys = getOrderByExpressions();
            List otherOrderBys = query.getOrderByExpressions();
            int size = orderBys.size();
            if (size != otherOrderBys.size()) {
                return false;
            }
            for (int index = 0; index < size; index++) {
                if (!orderBys.get(index).equals(otherOrderBys.get(index))) {
                    return false;
                }
            }
        } else if (query.hasOrderByExpressions()) {
            return false;
        }
        return true;
    }
    
    /**
     * PUBLIC:
     * Return if this is a read all query.
     */
    public boolean isReadAllQuery() {
        return true;
    }

    /**
     * INTERNAL:
     * Prepare the receiver for execution in a session.
     */
    protected void prepare() throws QueryException {
        if ((!isReportQuery()) && prepareFromCachedQuery()) {
            return;
        }
        super.prepare();

        getContainerPolicy().prepare(this, getSession());

        if (getContainerPolicy().overridesRead()) {
            return;
        }

        if (getDescriptor().isDescriptorForInterface()) {
            return;
        }

        prepareSelectAllRows();
        computeBatchReadMappingQueries();
    }
    
    /**
     * INTERNAL:
     * Prepare the query from the prepared query.
     * This allows a dynamic query to prepare itself directly from a prepared query instance.
     * This is used in the JPQL parse cache to allow preparsed queries to be used to prepare
     * dynamic queries.
     * This only copies over properties that are configured through JPQL.
     */
    public void prepareFromQuery(DatabaseQuery query) {
        super.prepareFromQuery(query);
        if (query.isReadAllQuery()) {
            ReadAllQuery readQuery = (ReadAllQuery)query;
            if (readQuery.hasOrderByExpressions()) {
                this.orderByExpressions = readQuery.orderByExpressions;
            }
            this.containerPolicy = readQuery.containerPolicy;
            if (readQuery.hasHierarchicalExpressions()) {
                this.orderSiblingsByExpressions = readQuery.getOrderSiblingsByExpressions();
                this.connectByExpression = readQuery.getConnectByExpression();
                this.startWithExpression = readQuery.getStartWithExpression();
            }
            if (readQuery.hasBatchReadAttributes()) {
                this.batchReadAttributeExpressions = readQuery.batchReadAttributeExpressions;
                this.batchReadMappingQueries = readQuery.batchReadMappingQueries;
                this.batchReadAttributes = readQuery.batchReadAttributes;
            }
        }
    }

    /**
     * INTERNAL:
     * Set the properties needed to be cascaded into the custom query.
     */
    protected void prepareCustomQuery(DatabaseQuery customQuery) {
        ReadAllQuery customReadQuery = (ReadAllQuery)customQuery;
        customReadQuery.setContainerPolicy(getContainerPolicy());
        customReadQuery.setCascadePolicy(getCascadePolicy());
        customReadQuery.setShouldRefreshIdentityMapResult(shouldRefreshIdentityMapResult());
        customReadQuery.setShouldMaintainCache(shouldMaintainCache());
        customReadQuery.setShouldUseWrapperPolicy(shouldUseWrapperPolicy());
    }

    /**
     * INTERNAL:
     * Prepare the receiver for execution in a session.
     */
    public void prepareForExecution() throws QueryException {
        super.prepareForExecution();

        getContainerPolicy().prepareForExecution();

    }

    /**
     * INTERNAL:
     * Prepare the mechanism.
     */
    protected void prepareSelectAllRows() {
        getQueryMechanism().prepareSelectAllRows();
    }

    /**
     * INTERNAL:
     * All objects queried via a UnitOfWork get registered here.  If the query
     * went to the database.
     * <p>
     * Involves registering the query result individually and in totality, and
     * hence refreshing / conforming is done here.
     *
     * @param result may be collection (read all) or an object (read one),
     * or even a cursor.  If in transaction the shared cache will
     * be bypassed, meaning the result may not be originals from the parent
     * but raw database rows.
     * @param unitOfWork the unitOfWork the result is being registered in.
     * @param arguments the original arguments/parameters passed to the query
     * execution.  Used by conforming
     * @param buildDirectlyFromRows If in transaction must construct
     * a registered result from raw database rows.
     *
     * @return the final (conformed, refreshed, wrapped) UnitOfWork query result
     */
    public Object registerResultInUnitOfWork(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord arguments, boolean buildDirectlyFromRows) {
        // For bug 2612366: Conforming results in UOW extremely slow.
        // Replacing results with registered versions in the UOW is a part of 
        // conforming and is now done while conforming to maximize performance.
        if (shouldConformResultsInUnitOfWork() || getDescriptor().shouldAlwaysConformResultsInUnitOfWork()) {
            return conformResult(result, unitOfWork, arguments, buildDirectlyFromRows);
        }

        // When building directly from rows, one of the performance benefits
        // is that we no longer have to wrap and then unwrap the originals.
        // result is just a vector, not a collection of wrapped originals.
        // Also for cursors the initial connection is automatically registered.
        if (buildDirectlyFromRows) {
            Vector rows = (Vector)result;
            ContainerPolicy cp = getContainerPolicy();
            Object clones = cp.containerInstance(rows.size());
            for (Enumeration enumtr = rows.elements(); enumtr.hasMoreElements();) {
                AbstractRecord row = (AbstractRecord)enumtr.nextElement();

                // null is placed in the row collection for 1-m joining to filter duplicate rows.
                if (row != null) {
                    Object clone = buildObject(row);
                    cp.addInto(clone, clones, unitOfWork);
                }
            }
            return clones;
        }

        ContainerPolicy cp;
        Cursor cursor = null;

        // If the query is redirected then the collection returned might no longer
        // correspond to the original container policy.  CR#2342-S.M.
        if (getRedirector() != null) {
            cp = ContainerPolicy.buildPolicyFor(result.getClass());
        } else {
            cp = getContainerPolicy();
        }

        // In the case of cursors just register the initially read collection.
        if (cp.isCursorPolicy()) {
            cursor = (Cursor)result;
            // In nested UnitOfWork session might have been session of the parent.
            cursor.setSession(unitOfWork);
            cp = ContainerPolicy.buildPolicyFor(ClassConstants.Vector_class);
            result = cursor.getObjectCollection();
        }

        Object clones = cp.containerInstance(cp.sizeFor(result));
        AbstractSession sessionToUse = unitOfWork.getParent();
        for (Object iter = cp.iteratorFor(result); cp.hasNext(iter);) {
            Object object = cp.next(iter, sessionToUse);
            Object clone = registerIndividualResult(object, unitOfWork, this.joinedAttributeManager);
            cp.addInto(clone, clones, unitOfWork);
        }
        if (cursor != null) {
            cursor.setObjectCollection((Vector)clones);
            return cursor;
        } else {
            return clones;
        }
    }

    /**
     * INTERNAL:
     * Execute the query through remote session.
     */
    public Object remoteExecute() {
        if (getContainerPolicy().overridesRead()) {
            return getContainerPolicy().remoteExecute();
        }

        Object cacheHit = checkEarlyReturn(getSession(), getTranslationRow());
        if (cacheHit != null) {
            return cacheHit;
        }

        return super.remoteExecute();
    }

    /**
     * INTERNAL:
     * replace the value holders in the specified result object(s)
     */
    public Map replaceValueHoldersIn(Object object, RemoteSessionController controller) {
        return controller.replaceValueHoldersInAll(object, getContainerPolicy());
    }

    /**
     * INTERNAL:
     * Return all attributes specified for batch reading.
     */
    public void setBatchReadAttributeExpressions(Vector batchReadAttributeExpressions) {
        this.batchReadAttributeExpressions = batchReadAttributeExpressions;
    }

    /**
     * PUBLIC:
     * Set the container policy. Used to support different containers
     * (e.g. Collections, Maps).
     */
    public void setContainerPolicy(ContainerPolicy containerPolicy) {
        // CR#... a container policy is always required, default is vector,
        // required for deployment XML.
        if (containerPolicy == null) {
            return;
        }
        this.containerPolicy = containerPolicy;
        setIsPrepared(false);
    }

    /**
     * PUBLIC:
     * Set the Hierarchical Query Clause for the query
     * <p>Example:
     * <p>Expression startWith = builder.get("id").equal(new Integer(100)); //can be any expression which identifies a set of employees
     * <p>Expression connectBy = builder.get("managedEmployees"); //indicated the relationship that the hierarchy is based on, must be self-referential
     * <p>Vector orderBy = new Vector();
     * <p>orderBy.addElement(builder.get("startDate"));
     * <p>readAllQuery.setHierarchicalQueryClause(startWith, connectBy, orderBy);
     *
     * <p>This query would generate SQL like this:
     * <p>SELECT * FROM EMPLOYEE START WITH ID=100 CONNECT BY PRIOR ID = MANAGER_ID ORDER SIBLINGS BY START_DATE
     * 
     * @param startWith Describes the START WITH clause of the query - null if not needed
     * @param connectBy This should be a query key expression which indicates an attribute who's mapping describes the hierarchy
     * @param orderSiblingsExpressions Contains expressions which indicate fields to be included in the ORDER SIBLINGS BY clause - null if not required
     */
    public void setHierarchicalQueryClause(Expression startWith, Expression connectBy, Vector orderSiblingsExpressions) {
        this.startWithExpression = startWith;
        this.connectByExpression = connectBy;
        this.orderSiblingsByExpressions = orderSiblingsExpressions;
        setIsPrepared(false);
    }

    /**
     * INTERNAL:
     * Set the order expressions for the query.
     */
    public void setOrderByExpressions(Vector orderByExpressions) {
        this.orderByExpressions = orderByExpressions;
    }

    /**
     * PUBLIC:
     * Configure the mapping to use an instance of the specified container class
     * to hold the target objects.
     * <p>jdk1.2.x: The container class must implement (directly or indirectly) the Collection interface.
     * <p>jdk1.1.x: The container class must be a subclass of Vector.
     */
    public void useCollectionClass(Class concreteClass) {
        // Set container policy.
        setContainerPolicy(ContainerPolicy.buildPolicyFor(concreteClass));

    }

    /**
     * PUBLIC:
     * Use a CursoredStream as the result collection.
     * The initial read size is 10 and page size is 5.
     */
    public void useCursoredStream() {
        useCursoredStream(10, 5);
    }

    /**
     * PUBLIC:
     * Use a CursoredStream as the result collection.
     * @param initialReadSize the initial number of objects to read
     * @param pageSize the number of objects to read when more objects
     * are needed from the database
     */
    public void useCursoredStream(int initialReadSize, int pageSize) {
        setContainerPolicy(new CursoredStreamPolicy(this, initialReadSize, pageSize));
    }

    /**
     * PUBLIC:
     * Use a CursoredStream as the result collection.
     * @param initialReadSize the initial number of objects to read
     * @param pageSize the number of objects to read when more objects
     * are needed from the database
     * @param sizeQuery a query that will return the size of the result set;
     * this must be set if an expression is not used (i.e. custom SQL)
     */
    public void useCursoredStream(int initialReadSize, int pageSize, ValueReadQuery sizeQuery) {
        setContainerPolicy(new CursoredStreamPolicy(this, initialReadSize, pageSize, sizeQuery));
    }

    /**
     * PUBLIC:
     * Configure the query to use an instance of the specified container class
     * to hold the result objects. The key used to index the value in the Map
     * is the value returned by a call to the specified zero-argument method.
     * The method must be implemented by the class (or a superclass) of the
     * value to be inserted into the Map.
     * <p>jdk1.2.x: The container class must implement (directly or indirectly) the Map interface.
     * <p>jdk1.1.x: The container class must be a subclass of Hashtable.
     * <p>The referenceClass must set before calling this method.
     */
    public void useMapClass(Class concreteClass, String methodName) {
        // the reference class has to be specified before coming here
        if (getReferenceClass() == null) {
            throw QueryException.referenceClassMissing(this);
        }
        ContainerPolicy policy = ContainerPolicy.buildPolicyFor(concreteClass);
        policy.setKeyName(methodName, getReferenceClass().getName());
        setContainerPolicy(policy);
    }

    /**
     * PUBLIC:
     * Use a ScrollableCursor as the result collection.
     */
    public void useScrollableCursor() {
        useScrollableCursor(10);
    }

    /**
     * PUBLIC:
     * Use a ScrollableCursor as the result collection.
     * @param pageSize the number of elements to be read into a the cursor
     * when more elements are needed from the database.
     */
    public void useScrollableCursor(int pageSize) {
        setContainerPolicy(new ScrollableCursorPolicy(this, pageSize));
    }

    /**
     * PUBLIC:
     * Use a ScrollableCursor as the result collection.
     * @param policy the scrollable cursor policy allows for additional result set options.
     * Example:<p>
     * ScrollableCursorPolicy policy = new ScrollableCursorPolicy()<p>
     * policy.setResultSetType(ScrollableCursorPolicy.TYPE_SCROLL_INSENSITIVE);<p>
     * query.useScrollableCursor(policy);<p>
     */
    public void useScrollableCursor(ScrollableCursorPolicy policy) {
        policy.setQuery(this);
        setContainerPolicy(policy);
    }
        
    /**
     * INTERNAL:
     * Used to optimize joining by pre-computing the nested join queries for the mappings.
     */
    public void computeBatchReadMappingQueries() {
        // Cannot prepare the batch queries if using inheritance, as child descriptors can have different mappings.
        if (hasBatchReadAttributes() && (!getDescriptor().hasInheritance())) {
            this.batchReadAttributes = new ArrayList(getBatchReadAttributeExpressions().size());
            setBatchReadMappingQueries(new HashMap(getBatchReadAttributeExpressions().size()));
            computeNestedQueriesForBatchReadExpressions(getBatchReadAttributeExpressions());
        }
    }
    
    /**
     * INTERNAL:
     * This method is used when computing the nested queries for batch read mappings.
     * It recurses computing the nested mapping queries.
     */
    protected void computeNestedQueriesForBatchReadExpressions(Vector batchReadExpressions) {
        for (int index = 0; index < batchReadExpressions.size(); index++) {
            ObjectExpression objectExpression = (ObjectExpression)batchReadExpressions.get(index);

            // Expression may not have been initialized.
            ExpressionBuilder builder = objectExpression.getBuilder();
            builder.setSession(getSession().getRootSession(null));
            builder.setQueryClass(getReferenceClass());            
            
            // PERF: Cache join attribute names.
            ObjectExpression baseExpression = objectExpression;
            while (!baseExpression.getBaseExpression().isExpressionBuilder()) {
                baseExpression = (ObjectExpression)baseExpression.getBaseExpression();
            }
            this.batchReadAttributes.add(baseExpression.getName());
            
            // Ignore nested
            if (objectExpression.getBaseExpression().isExpressionBuilder()) {
                DatabaseMapping mapping = objectExpression.getMapping();
                if ((mapping != null) && mapping.isForeignReferenceMapping()) {
                    // A nested query must be built to pass to the descriptor that looks like the real query execution would.
                    ReadQuery nestedQuery = ((ForeignReferenceMapping)mapping).prepareNestedBatchQuery(this);    
                    // Register the nested query to be used by the mapping for all the objects.
                    getBatchReadMappingQueries().put(mapping, nestedQuery);
                }
            }
        }
    }
}
