/*
 * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.queries;

import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.List;
import java.util.Map;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.expressions.ForUpdateClause;
import org.eclipse.persistence.internal.expressions.QueryKeyExpression;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.internal.queries.ContainerPolicy;
import org.eclipse.persistence.internal.queries.JoinedAttributeManager;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedClassForName;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.MergeManager;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.mappings.CollectionMapping;
import org.eclipse.persistence.mappings.DatabaseMapping;

/**
 * <p><b>Purpose</b>:
 * Abstract class for all read queries that build objects and potentially manipulate
 * the TopLink cache.
 *
 * <p><b>Description</b>:
 * Contains common behavior for all read queries building objects.
 *
 * @author Gordon Yorke
 * @since TopLink Essentials
 */
public abstract class ObjectBuildingQuery extends ReadQuery {

    /** The class of the target objects to be read from the database. */
    protected Class referenceClass;
    protected String referenceClassName;

    /** Allows for the resulting objects to be refresh with the data from the database. */
    protected boolean shouldRefreshIdentityMapResult;
    protected boolean shouldRefreshRemoteIdentityMapResult;

    /** INTERNAL: for bug 2612601 allow ability not to register results in UOW. */
    protected boolean shouldRegisterResultsInUnitOfWork = true;

    /** Used for pessimistic locking. */
    protected ForUpdateClause lockingClause;
    public static final short NO_LOCK = 0;
    public static final short LOCK = 1;
    public static final short LOCK_NOWAIT = 2;
    // allow pessimistic locking policy to be used
    public static final short DEFAULT_LOCK_MODE = -1;

    /**
     * Used to set the read time on objects that use this query.
     * Should be set to the time the query returned from the database.
     */
    protected long executionTime = 0;

    /**
     * Added for Exclusive Connection (VPD) support see accessor for information
     */
    protected boolean shouldUseExclusiveConnection = false;

    /**
     * INTERNAL:  This is the key for accessing unregistered and locked result in the query's properties.
     * The uow and  QueryBaseValueHolder use this property to record and to retrieve the result respectively.
     */
    public static final String LOCK_RESULT_PROPERTY = "LOCK_RESULT";

    /** PERF: Store if the query originally used the default lock mode. */
    protected boolean wasDefaultLockMode = false;

    /**
     * INTERNAL:  If primary key is null ObjectBuilder.buildObject returns null
     * in case this flag is set to true (instead of throwing exception).
     */
    protected boolean shouldBuildNullForNullPk;

    /**
     * When reading across relationships, queries may be set to acquire deferred locks
     * This is used to ensure any Eagerly fetched object that is the target of a relationship
     * with an object the acquires deferred locks behaves the same as its owner
     */
    protected Boolean requiresDeferredLocks = null;

    /** was a check early return completed */
    protected boolean isCacheCheckComplete;

    protected Map<Object, CacheKey> prefetchedCacheKeys;

    /**
     * INTERNAL:
     * Initialize the state of the query
     */
    public ObjectBuildingQuery() {
        this.shouldRefreshIdentityMapResult = false;
        this.isCacheCheckComplete = false;
    }

    /**
     * INTERNAL:
     * Clone the query
     */
    @Override
    public Object clone() {
        ObjectBuildingQuery cloneQuery = (ObjectBuildingQuery) super.clone();
        cloneQuery.isCacheCheckComplete = this.isCacheCheckComplete;
        return cloneQuery;
    }
    /**
     * INTERNAL
     * Used to give the subclasses opportunity to copy aspects of the cloned query
     * to the original query.
     */
    @Override
    protected void clonedQueryExecutionComplete(DatabaseQuery query, AbstractSession session) {
        super.clonedQueryExecutionComplete(query, session);
        //reset cache check flag for next execution.
        this.isCacheCheckComplete = false;
    }

    /**
     * INTERNAL:
     * Convert all the class-name-based settings in this query to actual class-based
     * settings. This method is used when converting a project that has been built
     * with class names to a project with classes.
     * @param classLoader
     */
    @Override
    public void convertClassNamesToClasses(ClassLoader classLoader){
        super.convertClassNamesToClasses(classLoader);
        Class referenceClass = null;
        try{
            if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                try {
                    referenceClass = AccessController.doPrivileged(new PrivilegedClassForName(getReferenceClassName(), true, classLoader));
                } catch (PrivilegedActionException exception) {
                    throw ValidationException.classNotFoundWhileConvertingClassNames(getReferenceClassName(), exception.getException());
                }
            } else {
                referenceClass = PrivilegedAccessHelper.getClassForName(getReferenceClassName(), true, classLoader);
            }
        } catch (ClassNotFoundException exc){
            throw ValidationException.classNotFoundWhileConvertingClassNames(getReferenceClassName(), exc);
        }
        setReferenceClass(referenceClass);
    };

    /**
     * INTERNAL:
     * Return if this query originally used the default lock mode.
     */
    protected boolean wasDefaultLockMode() {
        return wasDefaultLockMode;
    }

    /**
     * INTERNAL:
     * Set if this query originally used the default lock mode.
     */
    protected void setWasDefaultLockMode(boolean wasDefaultLockMode) {
        this.wasDefaultLockMode = wasDefaultLockMode;
    }

    /**
     * INTERNAL:
     * Clone the query, including its selection criteria.
     * <p>
     * Normally selection criteria are not cloned here as they are cloned
     * later on during prepare.
     */
    public Object deepClone() {
        return clone();
    }

    /**
     * INTERNAL:
     * Copy all setting from the query.
     * This is used to morph queries from one type to the other.
     * By default this calls prepareFromQuery, but additional properties may be required
     * to be copied as prepareFromQuery only copies properties that affect the SQL.
     */
    @Override
    public void copyFromQuery(DatabaseQuery query) {
        super.copyFromQuery(query);
        if (query.isObjectBuildingQuery()) {
            ObjectBuildingQuery readQuery = (ObjectBuildingQuery)query;
            this.shouldBuildNullForNullPk = readQuery.shouldBuildNullForNullPk;
            this.shouldRefreshIdentityMapResult = readQuery.shouldRefreshIdentityMapResult;
            this.shouldRefreshRemoteIdentityMapResult = readQuery.shouldRefreshRemoteIdentityMapResult;
            this.shouldRegisterResultsInUnitOfWork = readQuery.shouldRegisterResultsInUnitOfWork;
            this.shouldUseExclusiveConnection = readQuery.shouldUseExclusiveConnection;
        }
    }

    /**
     * INTERNAL:
     * Set the properties needed to be cascaded into the custom query including the translation row.
     * This is used only for primary key queries, as the descriptor query manager
     * stores a predefined query for this query to avoid having to re-prepare and allow for customization.
     */
    @Override
    protected void prepareCustomQuery(DatabaseQuery customQuery) {
        ((ObjectBuildingQuery)customQuery).isCacheCheckComplete = this.isCacheCheckComplete;
    }

    /**
     * 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 EJBQL parse cache to allow preparsed queries to be used to prepare
     * dynamic queries.
     * This only copies over properties that are configured through EJBQL.
     */
    @Override
    public void prepareFromQuery(DatabaseQuery query) {
        super.prepareFromQuery(query);
        if (query.isObjectBuildingQuery()) {
            ObjectBuildingQuery objectQuery = (ObjectBuildingQuery)query;
            this.referenceClass = objectQuery.referenceClass;
            this.referenceClassName = objectQuery.referenceClassName;
            this.lockingClause = objectQuery.lockingClause;
            this.wasDefaultLockMode = objectQuery.wasDefaultLockMode;
        }
    }

    /**
     * PUBLIC:
     * When unset means perform read normally and dont do refresh.
     */
    public void dontRefreshIdentityMapResult() {
        setShouldRefreshIdentityMapResult(false);
    }

    /**
     * PUBLIC:
     * When unset means perform read normally and dont do refresh.
     */
    public void dontRefreshRemoteIdentityMapResult() {
        setShouldRefreshRemoteIdentityMapResult(false);
    }

    /**
     * INTERNAL:
     * Indicates whether a FetchGroup will be applied to the query.
     */
    public boolean hasExecutionFetchGroup() {
        return false;
    }

    /**
     * INTERNAL:
     * Returns FetchGroup that will be applied to the query.
     * Note that the returned fetchGroup may be updated during preProcess.
     */
    public FetchGroup getExecutionFetchGroup() {
        return null;
    }

    /**
     * INTERNAL:
     * Returns FetchGroup that will be applied to the query.
     * Note that the returned fetchGroup may be updated during preProcess.
     */
    public FetchGroup getExecutionFetchGroup(ClassDescriptor descriptor) {
        return null;
    }

    /**
     * Return the load group set in the query.
     */
    public LoadGroup getLoadGroup() {
        return null;
    }

    /**
     * PUBLIC: Return the current locking mode.
     */
    public short getLockMode() {
        if (lockingClause == null) {
            return DEFAULT_LOCK_MODE;
        } else {
            return lockingClause.getLockMode();
        }
    }

    /**
     * INTERNAL:
     * Return  all of the rows fetched by the query, used for 1-m joining.
     */
    public List getDataResults() {
        return null;
    }

    /**
     * INTERNAL:
     * Return the time this query actually went to the database
     */
    public long getExecutionTime() {
        return executionTime;
    }

    public Map<Object, CacheKey> getPrefetchedCacheKeys() {
        return prefetchedCacheKeys;
    }

    /**
     * INTERNAL:
     * Return the primary key stored in this query if there is one
     * This is overridden by subclasses that actually hold a primary key
     *
     * @return
     *
     * @see ReadObjectQuery
     */
    protected Object getQueryPrimaryKey(){
        return null;
    }

    /**
     * PUBLIC:
     * Return the reference class of the query.
     */
    @Override
    public Class getReferenceClass() {
        return referenceClass;
    }

    /**
     * INTERNAL:
     * Return the reference class of the query.
     */
    @Override
    public String getReferenceClassName() {
        if ((referenceClassName == null) && (referenceClass != null)) {
            referenceClassName = referenceClass.getName();
        }
        return referenceClassName;
    }

    /**
     * INTERNAL:
     * Return if partial attributes.
     */
    public boolean hasPartialAttributeExpressions() {
        return false;
    }

    /**
     * PUBLIC:
     * Answers if the query lock mode is known to be LOCK or LOCK_NOWAIT.
     *
     * In the case of DEFAULT_LOCK_MODE and the query reference class being a CMP entity bean,
     * at execution time LOCK, LOCK_NOWAIT, or NO_LOCK will be decided.
     * <p>
     * If a single joined attribute was configured for pessimistic locking then
     * this will return true (after first execution) as the SQL contained a
     * FOR UPDATE OF clause.
     */
    public boolean isLockQuery() {
        return getLockMode() > NO_LOCK;
    }

    /**
     * PUBLIC:
     * Return if this is an object building query.
     */
    @Override
    public boolean isObjectBuildingQuery() {
        return true;
    }

    /**
     * INTERNAL:
     * Answers if we are executing through a UnitOfWork and registering results.
     * This is only ever false if using the conforming without registering
     * feature.
     */
    public boolean isRegisteringResults() {
        return ((shouldRegisterResultsInUnitOfWork() && this.descriptor.shouldRegisterResultsInUnitOfWork()) || isLockQuery());
    }

    /**
     * PUBLIC:
     * Refresh the attributes of the object(s) resulting from the query.
     * If cascading is used the private parts of the objects will also be refreshed.
     */
    public void refreshIdentityMapResult() {
        setShouldRefreshIdentityMapResult(true);
    }

    /**
     * PUBLIC:
     * Refresh the attributes of the object(s) resulting from the query.
     * If cascading is used the private parts of the objects will also be refreshed.
     */
    public void refreshRemoteIdentityMapResult() {
        setShouldRefreshRemoteIdentityMapResult(true);
    }

    /**
     * INTERNAL:
     * Constructs the final (registered) object for every individual object
     * queried via a UnitOfWork.
     * <p>
     * Called for every object in a read all, the object in a read object, and
     * every time the next or previous row is retrieved from a cursor.
     * <p>
     * The (conform) without registering feature is implemented here, and may
     * return an original non UnitOfWork registered result.
     * <p>
     * Pessimistically locked objects are tracked here.
     *
     * @return a refreshed UnitOfWork queried object, unwrapped.
     */
    public Object registerIndividualResult(Object result, Object primaryKey, UnitOfWorkImpl unitOfWork, JoinedAttributeManager joinManager, ClassDescriptor concreteDescriptor) {
        if (concreteDescriptor == null) {
            concreteDescriptor = unitOfWork.getDescriptor(result.getClass());
        }
        // PERF: Do not register nor process read-only.
        if (unitOfWork.isClassReadOnly(result.getClass(), concreteDescriptor)) {
            // There is an obscure case where they object could be read-only and pessimistic.
            // Record clone if referenced class has pessimistic locking policy.
            recordCloneForPessimisticLocking(result, unitOfWork);
            return result;
        }
        Object clone = null;
        // For bug 2612601 Conforming without registering in Unit Of Work.
        if (!isRegisteringResults()) {
            if (primaryKey == null) {
                primaryKey = concreteDescriptor.getObjectBuilder().extractPrimaryKeyFromObject(result, getSession());
            }
            clone = unitOfWork.getIdentityMapAccessorInstance().getIdentityMapManager().getFromIdentityMap(primaryKey, result.getClass(), concreteDescriptor);

            // If object not registered do not register it here!  Simply return
            // the original to the user.
            // Avoid setting clone = original, in case revert(clone) is called.
            if (clone == null) {
                clone = result;
            }
        } else {
            // bug # 3183379 either the object comes from the shared cache and is existing, or
            //it is from a parent unit of work and this unit of work does not need to know if it is new
            //or not.  It will query the parent unit of work to determine newness.

            clone = unitOfWork.registerExistingObject(result, concreteDescriptor, getQueryPrimaryKey(), true);
        }
        postRegisterIndividualResult(clone, result, primaryKey, unitOfWork, joinManager, concreteDescriptor);
        return clone;
    }

    /**
     * Post process the object once it is registered in the unit of work.
     */
    public void postRegisterIndividualResult(Object clone, Object original, Object primaryKey, UnitOfWorkImpl unitOfWork, JoinedAttributeManager joinManager, ClassDescriptor concreteDescriptor) {
        // Check for refreshing, require to revert in the unit of work to accomplish a refresh.
        if (shouldRefreshIdentityMapResult()) {

            if (shouldCascadeAllParts()) {
                unitOfWork.mergeClone(original, MergeManager.CASCADE_ALL_PARTS, true);
            } else if (shouldCascadePrivateParts()) {
                unitOfWork.mergeClone(original, MergeManager.CASCADE_PRIVATE_PARTS, true);
            } else if (shouldCascadeByMapping()) {
                unitOfWork.mergeClone(original, MergeManager.CASCADE_BY_MAPPING, true);
            } else if (!shouldCascadeParts()) {
                unitOfWork.mergeClone(original, MergeManager.NO_CASCADE, true);
            }
        }
        //bug 6130550: trigger indirection on the clone where required due to fetch joins on the query
        if (joinManager != null && joinManager.hasJoinedAttributeExpressions()) {
            triggerJoinExpressions(unitOfWork, joinManager, clone, concreteDescriptor);
        }
    }

    /**
     * INTERNAL:
     * Fetch/trigger indirection on the clone passed in, based on join expressions in the joinManager.
     */
    private void triggerJoinExpressions(UnitOfWorkImpl unitOfWork, JoinedAttributeManager joinManager, Object clone, ClassDescriptor concreteDescriptor) {
        List joinExpressions = joinManager.getJoinedAttributeExpressions();
        int size = joinExpressions.size();
        if ((size == 0) || (clone == null)) {
            return;
        }
        for (int index = 0; index < size; index++) {//since a's descriptor won't have a mapping for 'b'.
            //baseExpression will be first relationship expression after the ExpressionBuilder, and may include aggregate intermediaries
            QueryKeyExpression baseExpression = (QueryKeyExpression)joinManager.getJoinedAttributes().get(index);
            DatabaseMapping mapping = joinManager.getJoinedAttributeMappings().get(index);

            if (mapping != null) {
                Object attributeValue = joinManager.getValueFromObjectForExpression(unitOfWork, clone, baseExpression);
                if (attributeValue != null) {
                    //recurse through the mapping if the expression's base isn't the base expressionBuilder
                    QueryKeyExpression queryKeyExpression = (QueryKeyExpression)joinExpressions.get(index);
                    if (baseExpression != queryKeyExpression) {
                        ObjectLevelReadQuery nestedQuery = null;
                        if (joinManager.getJoinedMappingQueryClones() == null) {
                            if (joinManager.getJoinedMappingQueries_() != null) {
                                nestedQuery = joinManager.getJoinedMappingQueries_().get(mapping);
                            }
                        } else {
                            nestedQuery = joinManager.getJoinedMappingQueryClones().get(mapping);
                        }

                        //use the nestedQuery to trigger joins remaining for this expression (base onward)
                        if ((nestedQuery != null) && (nestedQuery.getJoinedAttributeManager() != null)) {
                            if (!mapping.isCollectionMapping()) {
                                triggerJoinExpressions(unitOfWork, nestedQuery.getJoinedAttributeManager(), attributeValue, null);
                            }else {
                                ContainerPolicy cp = ((CollectionMapping)mapping).getContainerPolicy();
                                Object iterator = cp.iteratorFor(attributeValue);
                                while (cp.hasNext(iterator)){
                                    triggerJoinExpressions(unitOfWork, nestedQuery.getJoinedAttributeManager(), cp.next(iterator, unitOfWork), null);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * INTERNAL:
     * When reading across relationships, queries may be set to acquire deferred locks
     * This is used to ensure any Eagerly fetched object that is the target of a relationship
     * with an object the acquires deferred locks behaves the same as its owner
     */
    public boolean requiresDeferredLocks() {
        return requiresDeferredLocks != null && requiresDeferredLocks.booleanValue();
    }

    /**
     * INTERNAL:
     * Set the the time this query went to the database.
     */
    public void setExecutionTime(long executionTime) {
        this.executionTime = executionTime;
    }

    /**
     * PUBLIC:
     * Sets whether this is a pessimistically locking query.
     * <ul>
     * <li>ObjectBuildingQuery.LOCK: SELECT .... FOR UPDATE issued.
     * <li>ObjectBuildingQuery.LOCK_NOWAIT: SELECT .... FOR UPDATE NO WAIT issued.
     * <li>ObjectBuildingQuery.NO_LOCK: no pessimistic locking.
     * <li>ObjectBuildingQuery.DEFAULT_LOCK_MODE (default) and you have a CMP descriptor:
     * fine grained locking will occur.
     * </ul>
     * <p>Fine Grained Locking: On execution the reference class
     * and those of all joined attributes will be checked.  If any of these have a
     * PessimisticLockingPolicy set on their descriptor, they will be locked in a
     * SELECT ... FOR UPDATE OF ... {NO WAIT}.  Issues fewer locks
     * and avoids setting the lock mode on each query.
     * <p>Example:<code>readAllQuery.setSelectionCriteria(employee.get("address").equal("Ottawa"));</code>
     * <ul><li>LOCK: all employees in Ottawa and all referenced Ottawa addresses will be locked.
     * <li>DEFAULT_LOCK_MODE: if address is a joined attribute, and only address has a pessimistic
     * locking policy, only referenced Ottawa addresses will be locked.
     * </ul>
     * @see org.eclipse.persistence.descriptors.PessimisticLockingPolicy
     */
    public void setLockMode(short lockMode) {
        lockingClause = ForUpdateClause.newInstance(lockMode);
    }

    public void setPrefetchedCacheKeys(Map<Object, CacheKey> prefetchedCacheKeys) {
        this.prefetchedCacheKeys = prefetchedCacheKeys;
    }

    /**
     * REQUIRED:
     * Set the reference class for the query.
     */
    public void setReferenceClass(Class aClass) {
        referenceClass = aClass;
        setIsPrepared(false);
    }

    /**
     * INTERNAL:
     * Set the reference class for the query.
     */
    public void setReferenceClassName(String aClass) {
        referenceClassName = aClass;
        setIsPrepared(false);
    }

    /**
     * INTERNAL:
     * When reading across relationships, queries may be set to acquire deferred locks
     * This is used to ensure any Eagerly fetched object that is the target of a relationship
     * with an object the acquires deferred locks behaves the same as its owner
     */
    public void setRequiresDeferredLocks(boolean cascadeDeferredLocks) {
        if (session != null && session.getProject().isQueryCacheForceDeferredLocks()) {
            this.requiresDeferredLocks = true;
        } else {
            this.requiresDeferredLocks = Boolean.valueOf(cascadeDeferredLocks);
        }
    }

    /**
     * PUBLIC:
     * Set if the attributes of the object(s) resulting from the query should be refreshed.
     * If cascading is used the private parts of the objects will also be refreshed.
     */
    public void setShouldRefreshIdentityMapResult(boolean shouldRefreshIdentityMapResult) {
        this.shouldRefreshIdentityMapResult = shouldRefreshIdentityMapResult;
        if (shouldRefreshIdentityMapResult) {
            setShouldRefreshRemoteIdentityMapResult(true);
        }
    }

    /**
     * PUBLIC:
     * Set if the attributes of the object(s) resulting from the query should be refreshed.
     * If cascading is used the private parts of the objects will also be refreshed.
     */
    public void setShouldRefreshRemoteIdentityMapResult(boolean shouldRefreshIdentityMapResult) {
        this.shouldRefreshRemoteIdentityMapResult = shouldRefreshIdentityMapResult;
    }

    /**
     * INTERNAL:
     * Set to false to have queries conform to a UnitOfWork without registering
     * any additional objects not already in that UnitOfWork.
     * @see #shouldRegisterResultsInUnitOfWork
     * @bug 2612601
     */
    public void setShouldRegisterResultsInUnitOfWork(boolean shouldRegisterResultsInUnitOfWork) {
        this.shouldRegisterResultsInUnitOfWork = shouldRegisterResultsInUnitOfWork;
    }

    /**
     * ADVANCED:
     * If the user has isolated data and specified that the client session should
     * use an exclusive connection then by setting this condition to true
     * EclipseLink will ensure that the query is executed through the exclusive
     * connection.  This may be required in certain cases.  An example being
     * where database security will prevent a query joining to a secure table
     * from returning the correct results when executed through the shared
     * connection.
     */
    public void setShouldUseExclusiveConnection(boolean shouldUseExclusiveConnection) {
        this.shouldUseExclusiveConnection = shouldUseExclusiveConnection;
    }

    /**
     * INTERNAL:
     * Allows one to do conforming in a UnitOfWork without registering.
     * Queries executed on a UnitOfWork will only return working copies for objects
     * that have already been registered.
     * <p>Extreme care should be taken in using this feature, for a user will
     * get back a mix of registered and original (unregistered) objects.
     * <p>Best used with a WrapperPolicy where invoking on an object will trigger
     * its registration (CMP).  Without a WrapperPolicy {@link org.eclipse.persistence.sessions.UnitOfWork#registerExistingObject registerExistingObject}
     * should be called on any object that you intend to change.
     * @return true by default.
     * @see #setShouldRegisterResultsInUnitOfWork
     * @bug 2612601
     */
    public boolean shouldRegisterResultsInUnitOfWork() {
        return shouldRegisterResultsInUnitOfWork;
    }

    /**
     * ADVANCED:
     * If the user has isolated data and specified that the client session should
     * use an exclusive connection then by setting this condition to true
     * EclipseLink will ensure that the query is executed through the exclusive
     * connection.  This may be required in certain cases.  An example being
     * where database security will prevent a query joining to a secure table
     * from returning the correct results when executed through the shared
     * connection.
     */
    public boolean shouldUseExclusiveConnection() {
        return this.shouldUseExclusiveConnection;
    }

    /**
     * INTERNAL:
     * Return if this is a full object query, not partial nor fetch group.
     */
    public boolean shouldReadAllMappings() {
        return true;
    }

    /**
     * INTERNAL:
     * Check if the mapping is part of the partial attributes.
     */
    public boolean shouldReadMapping(DatabaseMapping mapping, FetchGroup fetchGroup) {
        return true;
    }

    /**
     * PUBLIC:
     * Set to a boolean. When set means refresh the instance
     * variables of referenceObject from the database.
     */
    public boolean shouldRefreshIdentityMapResult() {
        return shouldRefreshIdentityMapResult;
    }

    /**
     * PUBLIC:
     * Set to a boolean. When set means refresh the instance
     * variables of referenceObject from the database.
     */
    public boolean shouldRefreshRemoteIdentityMapResult() {
        return shouldRefreshRemoteIdentityMapResult;
    }

    /**
     * INTERNAL:
     * Return if the attribute is specified for joining.
     */
    public boolean isAttributeJoined(ClassDescriptor mappingDescriptor, String attributeName) {
        return false;
    }

    /**
     * INTERNAL:
     * Returns true if an early return cache check was completed
     */
    public boolean isCacheCheckComplete(){
        return this.isCacheCheckComplete;
    }

    /**
    * INTERNAL:
    * Helper method that checks if clone has been locked with uow.
    */
    public boolean isClonePessimisticLocked(Object clone, UnitOfWorkImpl uow) {
        return false;
    }

    /**
     * INTERNAL:
     * Helper method that records clone with uow if query is pessimistic locking.
     */
    public void recordCloneForPessimisticLocking(Object clone, UnitOfWorkImpl uow) {
        if ((isLockQuery()) && lockingClause.isReferenceClassLocked()) {
            uow.addPessimisticLockedClone(clone);
        }
    }

    /**
    * INTERNAL: Helper method to determine the default mode. If true and quey has a pessimistic locking policy,
    * locking will be configured according to the pessimistic locking policy.
    */
    public boolean isDefaultLock() {
        return (lockingClause == null);
    }

    /**
     * INTERNAL:
     * If primary key is null ObjectBuilder.buildObject returns null
     * in case this flag is set to true (instead of throwing exception).
     */
    public boolean shouldBuildNullForNullPk() {
        return shouldBuildNullForNullPk;
    }

    /**
     * INTERNAL:
     * If primary key is null ObjectBuilder.buildObject returns null
     * in case this flag is set to true (instead of throwing exception).
     */
    public void setShouldBuildNullForNullPk(boolean shouldBuildNullForNullPk) {
        this.shouldBuildNullForNullPk = shouldBuildNullForNullPk;
    }
    /**
     * INTERNAL:
     * Return if the query uses ResultSet optimization.
     */
    public boolean usesResultSetAccessOptimization() {
        return false;
    }

    /**
     * INTERNAL:
     * Indicates whether the query should use SerializedObjectPolicy if descriptor has it.
     */
    public boolean shouldUseSerializedObjectPolicy() {
        return false;
    }
}
