| /* |
| * 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.util.Set; |
| |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.CommitManager; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.mappings.DatabaseMapping.WriteType; |
| import org.eclipse.persistence.exceptions.*; |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.descriptors.DescriptorEvent; |
| import org.eclipse.persistence.descriptors.DescriptorEventManager; |
| import org.eclipse.persistence.descriptors.DescriptorQueryManager; |
| import org.eclipse.persistence.descriptors.FetchGroupManager; |
| import org.eclipse.persistence.tools.profiler.QueryMonitor; |
| |
| /** |
| * <p><b>Purpose</b>: Used for deleting objects. |
| * |
| * <p><b>Responsibilities</b>: |
| * Extract primary key from object and delete it. |
| * |
| * @author Yvon Lavoie |
| * @since TOPLink/Java 1.0 |
| */ |
| public class DeleteObjectQuery extends ObjectLevelModifyQuery { |
| /** PERF: By default only the translation row is used for deletes, the full row can be requested for custom deletes. */ |
| protected boolean isFullRowRequired = false; |
| |
| /** Indicates whether the query should use optimistic locking. */ |
| protected boolean usesOptimisticLocking; |
| |
| public DeleteObjectQuery() { |
| super(); |
| } |
| |
| public DeleteObjectQuery(Object objectToDelete) { |
| this(); |
| setObject(objectToDelete); |
| } |
| |
| public DeleteObjectQuery(Call call) { |
| this(); |
| setCall(call); |
| } |
| |
| /** |
| * ADVANCED: |
| * Return if the full row is required by the delete query. |
| * This can be set on custom delete queries if more than the objects primary key and version is required. |
| */ |
| public boolean isFullRowRequired() |
| { |
| return isFullRowRequired; |
| } |
| |
| /** |
| * ADVANCED: |
| * Set if the full row is required by the delete query. |
| * This can be set on custom delete queries if more than the objects primary key and version is required. |
| */ |
| public void setIsFullRowRequired(boolean isFullRowRequired) |
| { |
| this.isFullRowRequired = isFullRowRequired; |
| } |
| |
| /** |
| * 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. |
| */ |
| @Override |
| protected DatabaseQuery checkForCustomQuery(AbstractSession session, AbstractRecord translationRow) { |
| checkDescriptor(session); |
| |
| // check if user defined a custom query |
| DescriptorQueryManager queryManager = this.descriptor.getQueryManager(); |
| if ((!isCallQuery())// this is not a hand-coded (custom SQL, SDK etc.) call |
| && (!isUserDefined())// and this is not a user-defined query (in the query manager) |
| && queryManager.hasDeleteQuery()) {// and there is a user-defined query (in the query manager) |
| return queryManager.getDeleteQuery(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Code was moved from UnitOfWork.internalExecuteQuery |
| * |
| */ |
| @Override |
| protected Object executeInUnitOfWorkObjectLevelModifyQuery(UnitOfWorkImpl unitOfWork, AbstractRecord translationRow) throws DatabaseException, OptimisticLockException { |
| Object result = unitOfWork.processDeleteObjectQuery(this); |
| if (result != null) { |
| // if the above method returned something then the unit of work |
| //was not writing so the object has been stored to delete later |
| //so return the object. See the above method for the cases |
| //where this object will be returned. |
| return result; |
| } |
| return super.executeInUnitOfWorkObjectLevelModifyQuery(unitOfWork, translationRow); |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the specific default redirector for this query type. There are numerous default query redirectors. |
| * See ClassDescriptor for their types. |
| */ |
| @Override |
| protected QueryRedirector getDefaultRedirector(){ |
| return descriptor.getDefaultDeleteObjectQueryRedirector(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Perform the work to delete an object. |
| * @return object - the object being deleted. |
| */ |
| @Override |
| public Object executeDatabaseQuery() throws DatabaseException, OptimisticLockException { |
| AbstractSession session = getSession(); |
| CommitManager commitManager = session.getCommitManager(); |
| Object object = getObject(); |
| boolean isUnitOfWork = session.isUnitOfWork(); |
| try { |
| // Check if the object has already been deleted, then no work is required. |
| if (commitManager.isCommitCompletedInPostOrIgnore(object)) { |
| return object; |
| } |
| ClassDescriptor descriptor = this.descriptor; |
| // Check whether the object is already being deleted, |
| // if it is, then there is a cycle, and the foreign keys must be nulled. |
| if (commitManager.isCommitInPreModify(object)) { |
| if (!commitManager.isShallowCommitted(object)) { |
| getQueryMechanism().updateForeignKeyFieldBeforeDelete(); |
| if ((descriptor.getHistoryPolicy() != null) && descriptor.getHistoryPolicy().shouldHandleWrites()) { |
| descriptor.getHistoryPolicy().postUpdate(this); |
| } |
| commitManager.markShallowCommit(object); |
| } |
| return object; |
| } |
| commitManager.markPreModifyCommitInProgress(getObject()); |
| if (!isUnitOfWork) { |
| session.beginTransaction(); |
| } |
| DescriptorEventManager eventManager = descriptor.getEventManager(); |
| // PERF: Avoid events if no listeners. |
| if (eventManager.hasAnyEventListeners()) { |
| // Need to run pre-delete selector if available |
| eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PreDeleteEvent, this)); |
| } |
| |
| // Verify if deep shallow modify is turned on |
| if (shouldCascadeParts()) { |
| descriptor.getQueryManager().preDelete(this); |
| } |
| |
| // Check for deletion dependencies. |
| if (isUnitOfWork) { |
| Set<Object> dependencies = ((UnitOfWorkImpl)session).getDeletionDependencies(object); |
| if (dependencies != null) { |
| for (Object dependency : dependencies) { |
| if (!commitManager.isCommitCompletedInPostOrIgnore(dependency)) { |
| ClassDescriptor dependencyDescriptor = session.getDescriptor(dependency); |
| // PERF: Get the descriptor query, to avoid extra query creation. |
| DeleteObjectQuery deleteQuery = dependencyDescriptor.getQueryManager().getDeleteQuery(); |
| if (deleteQuery == null) { |
| deleteQuery = new DeleteObjectQuery(); |
| deleteQuery.setDescriptor(dependencyDescriptor); |
| } else { |
| // Ensure original query has been prepared. |
| deleteQuery.checkPrepare(session, deleteQuery.getTranslationRow()); |
| deleteQuery = (DeleteObjectQuery)deleteQuery.clone(); |
| } |
| deleteQuery.setIsExecutionClone(true); |
| deleteQuery.setObject(dependency); |
| session.executeQuery(deleteQuery); |
| } |
| } |
| } |
| } |
| |
| // CR#2660080 missing aboutToDelete event. |
| // PERF: Avoid events if no listeners. |
| if (eventManager.hasAnyEventListeners()) { |
| DescriptorEvent event = new DescriptorEvent(DescriptorEventManager.AboutToDeleteEvent, this); |
| event.setRecord(getModifyRow()); |
| eventManager.executeEvent(event); |
| } |
| |
| if (QueryMonitor.shouldMonitor()) { |
| QueryMonitor.incrementDelete(this); |
| } |
| int rowCount = 0; |
| // If the object was/will be deleted from a cascade delete constraint, ignore it. |
| if (isUnitOfWork && ((UnitOfWorkImpl)session).hasCascadeDeleteObjects() |
| && ((UnitOfWorkImpl)session).getCascadeDeleteObjects().contains(object)) { |
| // Cascade delete does not check optimistic lock, assume ok. |
| rowCount = 1; |
| } else { |
| rowCount = getQueryMechanism().deleteObject(); |
| } |
| |
| if (rowCount < 1) { |
| if (session.hasEventManager()) { |
| session.getEventManager().noRowsModified(this, object); |
| } |
| } |
| |
| if (this.usesOptimisticLocking) { |
| descriptor.getOptimisticLockingPolicy().validateDelete(rowCount, object, this); |
| } |
| |
| commitManager.markPostModifyCommitInProgress(getObject()); |
| // Verify if deep shallow modify is turned on |
| if (shouldCascadeParts()) { |
| descriptor.getQueryManager().postDelete(this); |
| } |
| |
| if ((descriptor.getHistoryPolicy() != null) && descriptor.getHistoryPolicy().shouldHandleWrites()) { |
| descriptor.getHistoryPolicy().postDelete(this); |
| } |
| |
| // PERF: Avoid events if no listeners. |
| if (eventManager.hasAnyEventListeners()) { |
| // Need to run post-delete selector if available |
| eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PostDeleteEvent, this)); |
| } |
| |
| if (!isUnitOfWork) { |
| session.commitTransaction(); |
| } |
| commitManager.markCommitCompleted(object); |
| |
| // CR3510313, avoid removing aggregate collections from cache (maintain cache is false). |
| if (shouldMaintainCache()) { |
| if (isUnitOfWork) { |
| ((UnitOfWorkImpl)session).addObjectDeletedDuringCommit(object, descriptor); |
| } else { |
| session.getIdentityMapAccessorInstance().removeFromIdentityMap(getPrimaryKey(), descriptor.getJavaClass(), descriptor, object); |
| } |
| } |
| return object; |
| |
| } catch (RuntimeException exception) { |
| if (!isUnitOfWork) { |
| session.rollbackTransaction(); |
| } |
| commitManager.markCommitCompleted(object); |
| throw exception; |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Return if this is a delete object query. |
| */ |
| @Override |
| public boolean isDeleteObjectQuery() { |
| return true; |
| } |
| |
| /** |
| * PUBLIC: (REQUIRED) |
| * Set the object required for modification. |
| */ |
| @Override |
| public void setObject(Object object) { |
| if (isPrepared()) { |
| if (this.usesOptimisticLocking != shouldUseOptimisticLocking(object)) { |
| setIsPrepared(false); |
| } |
| } |
| super.setObject(object); |
| } |
| |
| /** |
| * INTERNAL: |
| * Determines whether the query should use optimistic locking with the passed object. |
| */ |
| protected boolean shouldUseOptimisticLocking(Object object) { |
| if (this.descriptor.usesOptimisticLocking()) { |
| if (object != null) { |
| FetchGroupManager fetchGroupManager = this.descriptor.getFetchGroupManager(); |
| if (fetchGroupManager != null) { |
| FetchGroup fetchGroup = fetchGroupManager.getObjectFetchGroup(object); |
| if (fetchGroup == fetchGroupManager.getIdEntityFetchGroup()) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicating whether the query should use optimistic locking. |
| */ |
| public boolean usesOptimisticLocking() { |
| return this.usesOptimisticLocking; |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare the receiver for execution in a session. |
| */ |
| @Override |
| protected void prepare() { |
| super.prepare(); |
| this.usesOptimisticLocking = shouldUseOptimisticLocking(this.object); |
| if (this.name == null) { |
| this.name = "delete" + this.descriptor.getJavaClass().getSimpleName(); |
| } |
| getQueryMechanism().prepareDeleteObject(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the properties needed to be cascaded into the custom query. |
| * A custom query is set by the user, or used by default to allow caching of the prepared query. |
| * In a unit of work the custom query is used directly, so this step is bypassed. |
| */ |
| @Override |
| protected void prepareCustomQuery(DatabaseQuery customQuery) { |
| DeleteObjectQuery customDeleteQuery = (DeleteObjectQuery)customQuery; |
| customDeleteQuery.setObject(getObject()); |
| customDeleteQuery.setObjectChangeSet(getObjectChangeSet()); |
| customDeleteQuery.setCascadePolicy(getCascadePolicy()); |
| customDeleteQuery.setShouldMaintainCache(shouldMaintainCache()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare the receiver for execution in a session. In particular, |
| * verify that the object is not null and contains a valid primary key. |
| */ |
| @Override |
| public void prepareForExecution() throws QueryException { |
| super.prepareForExecution(); |
| |
| // Set the translation row |
| if ((this.translationRow == null) || this.translationRow.isEmpty()) { |
| if (this.isFullRowRequired) { |
| this.translationRow = this.descriptor.getObjectBuilder().buildRow(this.object, this.session, WriteType.UNDEFINED); |
| } else { |
| this.translationRow = this.descriptor.getObjectBuilder().buildRowForTranslation(this.object, this.session); |
| } |
| } |
| |
| // Add the write lock field if required |
| if (this.usesOptimisticLocking) { |
| this.descriptor.getOptimisticLockingPolicy().addLockValuesToTranslationRow(this); |
| } |
| } |
| } |