/*
 * Copyright (c) 1998, 2018 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
//     07/16/2009-2.0 Guy Pelletier
//       - 277039: JPA 2.0 Cache Usage Settings
package org.eclipse.persistence.queries;

import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.expressions.*;
import org.eclipse.persistence.internal.queries.DatabaseQueryMechanism;
import org.eclipse.persistence.internal.queries.ExpressionQueryMechanism;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.internal.sessions.AbstractSession;

/**
 * PUBLIC:
 * Query used to perform a bulk delete using the expression framework.
 *
 * @author Andrei Ilitchev
 * @since August 18, 2005
 */
public abstract class ModifyAllQuery extends ModifyQuery {

    /** Cache usage flags */
    public static final int NO_CACHE = 0;
    public static final int INVALIDATE_CACHE = 1;

    private int m_cacheUsage = INVALIDATE_CACHE;

    protected Class referenceClass;
    protected String referenceClassName;

    /** Number of modified objects */
    protected transient Integer result;

    /** Indicates whether execution should be deferred in UOW */
    private boolean shouldDeferExecutionInUOW;

    /** Provide a default builder so that it's easier to be consistent */
    protected ExpressionBuilder defaultBuilder;

    /** Indicates whether the query was prepared so that it will execute using temp storage */
    protected boolean isPreparedUsingTempStorage;

    /**
     * PUBLIC:
     */
    public ModifyAllQuery() {
        super();
        shouldDeferExecutionInUOW = true;
    }

    /**
     * PUBLIC:
     * Create a new update all query for the class specified.
     */
    public ModifyAllQuery(Class referenceClass) {
        this();
        setReferenceClass(referenceClass);
    }

    /**
     * PUBLIC:
     * Create a new update all query for the class and the selection criteria
     * specified.
     */
    public ModifyAllQuery(Class referenceClass, Expression selectionCriteria) {
        this();
        setReferenceClass(referenceClass);
        setSelectionCriteria(selectionCriteria);
    }

    /**
     * PUBLIC:
     * Return true if this is a modify all query.
     */
    @Override
    public boolean isModifyAllQuery() {
        return true;
    }

    /**
     * INTERNAL:
     */
    public void setIsPreparedUsingTempStorage(boolean isPreparedUsingTempStorage) {
        this.isPreparedUsingTempStorage = isPreparedUsingTempStorage;
    }

    /**
     * INTERNAL:
     */
    public boolean isPreparedUsingTempStorage() {
        return isPreparedUsingTempStorage;
    }

    /**
     * INTERNAL
     * Used to give the subclasses opportunity to copy aspects of the cloned query
     * to the original query.  The clones of all the ModifyAllQueries will be added to modifyAllQueries for validation.
     */
    @Override
    protected void clonedQueryExecutionComplete(DatabaseQuery query, AbstractSession session) {
        super.clonedQueryExecutionComplete(query, session);

        if (session.isUnitOfWork()) {
            ((UnitOfWorkImpl)session).storeModifyAllQuery(query);
        }
    }

    /**
     * INTERNAL:
     * Override query execution where Session is a UnitOfWork.
     * <p>
     * If there are objects in the cache return the results of the cache lookup.
     *
     * @param unitOfWork - the session in which the receiver will be executed.
     * @param translationRow - the arguments
     * @exception  DatabaseException - an error has occurred on the database.
     * @exception  OptimisticLockException - an error has occurred using the optimistic lock feature.
     * @return An object, the result of executing the query.
     */
    @Override
    public Object executeInUnitOfWork(UnitOfWorkImpl unitOfWork, AbstractRecord translationRow) throws DatabaseException, OptimisticLockException {
        if (unitOfWork.isNestedUnitOfWork()) {
            throw ValidationException.nestedUOWNotSupportedForModifyAllQuery();
        }

        //Bug4607551  For UpdateAllQuery, if deferred, add the original query with a translation row to the deferredUpdateAllQueries for execution.
        //No action for non-deferred.  Later on the clones of all the UpdateAllQuery's will be added to modifyAllQueries for validation.
        if(shouldDeferExecutionInUOW()) {
            unitOfWork.storeDeferredModifyAllQuery(this, translationRow);
            result = null;
        } else {
            if(!unitOfWork.isInTransaction()) {
                unitOfWork.beginEarlyTransaction();
            }
            unitOfWork.setWasNonObjectLevelModifyQueryExecuted(true);
            result = (Integer)super.executeInUnitOfWork(unitOfWork, translationRow);
        }
        return result;
    }

    /**
     * PUBLIC:
     * Return the cache usage for this query.
     */
    public int getCacheUsage() {
        return m_cacheUsage;
    }

    /**
     * PUBLIC:
     * Get the expression builder which should be used for this query.
     * This expression builder should be used to build all expressions used by this query.
     */
    public ExpressionBuilder getExpressionBuilder() {
        if (defaultBuilder == null) {
            initializeDefaultBuilder();
        }

        return defaultBuilder;
    }

    /**
     * INTERNAL
     * Sets the default expression builder for this query.
     */
    public void setExpressionBuilder(ExpressionBuilder builder) {
        this.defaultBuilder = builder;
    }

    /**
     * INTERNAL:
     * Return the name of the reference class of the query.
     * Used by the Mapping Workbench to avoid classpath dependencies
     */
    @Override
    public String getReferenceClassName() {
        if ((referenceClassName == null) && (referenceClass != null)) {
            referenceClassName = referenceClass.getName();
        }
        return referenceClassName;
    }

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

    /**
     * INTERNAL:
     * Invalid the cache, that is, those objects in the cache that were affected
     * by the query.
     */
    protected void invalidateCache() {
        if(result != null && result.intValue() == 0) {
            // no rows modified in the db - nothing to invalidate
            return;
        }
        getSession().getIdentityMapAccessor().invalidateObjects(getSelectionCriteria(), getReferenceClass(), getTranslationRow(), true);
    }

    /**
     * INTERNAL:
     * After execution we need to merge the changes into the shared cache,
     * unless the query has been tagged to bypass on the store.
     */
    public void mergeChangesIntoSharedCache() {
        if (shouldInvalidateCache() && ! shouldStoreBypassCache()) {
            invalidateCache();
        }
    }

    /**
     * PUBLIC:
     * Set the level of cache support for this query, either NONE or INVALIDATE.
     */
    public void setCacheUsage(int cacheUsage) {
        m_cacheUsage = cacheUsage;
    }

    /**
     * PUBLIC:
     * Set the reference class this query.
     */
    public void setReferenceClass(Class referenceClass) {
        if (this.referenceClass != referenceClass) {
            setIsPrepared(false);
        }
        this.referenceClass = referenceClass;
    }

    /**
     * INTERNAL:
     * Set the class name of the reference class of this query.
     * Used by the Mapping Workbench to avoid classpath dependencies.
     */
    public void setReferenceClassName(String className) {
        referenceClassName = className;
    }

    /**
     * PUBLIC:
     * Set a flag indicating whether execution should be deferred in UOW until commit.
     */
    public void setShouldDeferExecutionInUOW(boolean shouldDeferExecutionInUOW) {
        this.shouldDeferExecutionInUOW = shouldDeferExecutionInUOW;
    }

    /**
     * PUBLIC:
     * Indicates whether execution should be deferred in UOW until commit.
     */
    public boolean shouldDeferExecutionInUOW() {
        return shouldDeferExecutionInUOW;
    }

    /**
     * INTERNAL:
     */
    protected boolean shouldInvalidateCache() {
        return m_cacheUsage == INVALIDATE_CACHE;
    }

    /**
     * INTERNAL:
     * Initialize the expression builder which should be used for this query. If
     * there is a where clause, use its expression builder, otherwise
     * generate one and cache it. This helps avoid unnecessary rebuilds.
     */
    protected void initializeDefaultBuilder() {
        initializeQuerySpecificDefaultBuilder();
        if(defaultBuilder == null) {
            defaultBuilder = new ExpressionBuilder();
        }
    }

    /**
     * INTERNAL:
     * Initialize the expression builder which should be used for this query. If
     * there is a where clause, use its expression builder.
     * If after this method defaultBuilder is still null,
     * then initializeDefaultBuilder method will generate and cache it.
     */
    protected void initializeQuerySpecificDefaultBuilder() {
        DatabaseQueryMechanism mech = getQueryMechanism();
        if (mech.isExpressionQueryMechanism() && ((ExpressionQueryMechanism)mech).getExpressionBuilder() != null) {
            this.defaultBuilder = ((ExpressionQueryMechanism)mech).getExpressionBuilder();
        }
    }
}
