| /* |
| * 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.descriptors.changetracking; |
| |
| import java.beans.PropertyChangeListener; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.descriptors.DescriptorEvent; |
| import org.eclipse.persistence.descriptors.DescriptorEventManager; |
| import org.eclipse.persistence.exceptions.ValidationException; |
| import org.eclipse.persistence.internal.descriptors.ObjectBuilder; |
| import org.eclipse.persistence.internal.descriptors.changetracking.ObjectChangeListener; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.MergeManager; |
| import org.eclipse.persistence.internal.sessions.ObjectChangeSet; |
| import org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.mappings.ForeignReferenceMapping; |
| import org.eclipse.persistence.queries.FetchGroup; |
| import org.eclipse.persistence.queries.WriteObjectQuery; |
| |
| /** |
| * PUBLIC: |
| * A DeferredChangeDetectionPolicy defers all change detection to the UnitOfWork's |
| * change detection process. Essentially, the calculateChanges() method will run |
| * for all objects in a UnitOfWork. This is the default ObjectChangePolicy unless weaving is used. |
| * |
| * @author Tom Ware |
| */ |
| public class DeferredChangeDetectionPolicy implements ObjectChangePolicy, java.io.Serializable { |
| |
| /** |
| * INTERNAL: |
| * PERF: Calculate change for the new object, avoids check for new since already know. |
| */ |
| @Override |
| public ObjectChangeSet calculateChangesForNewObject(Object clone, UnitOfWorkChangeSet changeSet, UnitOfWorkImpl unitOfWork, ClassDescriptor descriptor, boolean shouldRaiseEvent) { |
| return calculateChanges(clone, null, true, changeSet, unitOfWork, descriptor, shouldRaiseEvent); |
| } |
| |
| /** |
| * INTERNAL: |
| * PERF: Calculate change for the new object, avoids check for new since already know. |
| */ |
| @Override |
| public ObjectChangeSet calculateChangesForExistingObject(Object clone, UnitOfWorkChangeSet changeSet, UnitOfWorkImpl unitOfWork, ClassDescriptor descriptor, boolean shouldRaiseEvent) { |
| return calculateChanges(clone, unitOfWork.getBackupClone(clone, descriptor), false, changeSet, unitOfWork, descriptor, shouldRaiseEvent); |
| } |
| |
| /** |
| * INTERNAL: |
| * calculateChanges creates a change set for a particular object. In DeferredChangeDetectionPolicy |
| * all mappings will be compared against a backup copy of the object. |
| * @return an object change set describing |
| * the changes to this object |
| * @param clone the Object to compute a change set for |
| * @param backUp the old version of the object to use for comparison |
| * @param changeSet the change set to add changes to |
| * @param unitOfWork the current session |
| * @param descriptor the descriptor for this object |
| * @param shouldRaiseEvent indicates whether PreUpdate event should be risen (usually true) |
| */ |
| @Override |
| public ObjectChangeSet calculateChanges(Object clone, Object backUp, boolean isNew, UnitOfWorkChangeSet changeSet, UnitOfWorkImpl unitOfWork, ClassDescriptor descriptor, boolean shouldRaiseEvent) { |
| // PERF: Avoid events if no listeners. |
| if (descriptor.getEventManager().hasAnyEventListeners() && shouldRaiseEvent) { |
| WriteObjectQuery writeQuery = new WriteObjectQuery(clone.getClass()); |
| writeQuery.setObject(clone); |
| writeQuery.setBackupClone(backUp); |
| writeQuery.setSession(unitOfWork); |
| writeQuery.setDescriptor(descriptor); |
| |
| descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreWriteEvent, writeQuery)); |
| |
| if (isNew) { |
| descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreInsertEvent, writeQuery)); |
| } else { |
| descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreUpdateEvent, writeQuery)); |
| } |
| } |
| |
| ObjectChangeSet changes = createObjectChangeSet(clone, backUp, changeSet, isNew, unitOfWork, descriptor); |
| if (changes.hasChanges()) { |
| if (descriptor.hasMappingsPostCalculateChanges() && ! changes.isNew() && ! unitOfWork.getCommitManager().isActive() && !unitOfWork.isNestedUnitOfWork()) { |
| // if we are in the commit because of an event skip this postCalculateChanges step as we have already executed it. |
| int size = descriptor.getMappingsPostCalculateChanges().size(); |
| for (int i=0; i < size; i++) { |
| DatabaseMapping mapping = descriptor.getMappingsPostCalculateChanges().get(i); |
| org.eclipse.persistence.sessions.changesets.ChangeRecord record = changes.getChangesForAttributeNamed(mapping.getAttributeName()); |
| if (record != null) { |
| // Deferred attributes will already have been acted on, therefore we need |
| // to post calculate changes to ensure orphaned objects are removed. |
| mapping.postCalculateChanges(record, unitOfWork); |
| } |
| } |
| } |
| } |
| //Check if the user set the PK to null and throw an exception (bug# 4569755) |
| if (changes.getId() == null && !isNew && !changes.isAggregate()) { |
| if(!(unitOfWork.isNestedUnitOfWork()) || (unitOfWork.isNestedUnitOfWork() && !(unitOfWork.isNewObjectInParent(clone)|| unitOfWork.isUnregisteredNewObjectInParent(unitOfWork.getCloneToOriginals().get(clone))))) { |
| Object id = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(clone, unitOfWork, false); |
| throw ValidationException.nullPrimaryKeyInUnitOfWorkClone(clone, id); |
| } |
| } |
| |
| // if forceUpdate or optimistic read locking is on, mark changeSet. This is to force it |
| // to be stored and used for writing out SQL later on |
| if ((descriptor.getCMPPolicy() != null) && (descriptor.getCMPPolicy().getForceUpdate())) { |
| changes.setHasCmpPolicyForcedUpdate(true); |
| } |
| if (!changes.hasForcedChangesFromCascadeLocking() && unitOfWork.hasOptimisticReadLockObjects()) { |
| Boolean modifyVersionField = (Boolean)unitOfWork.getOptimisticReadLockObjects().get(clone); |
| if ((modifyVersionField != null) && (unitOfWork instanceof RepeatableWriteUnitOfWork) && (((RepeatableWriteUnitOfWork)unitOfWork).getCumulativeUOWChangeSet() != null)) { |
| // modify the version field if the UOW cumulative change set does not contain a changeset for this clone |
| if (((RepeatableWriteUnitOfWork)unitOfWork).getCumulativeUOWChangeSet().getObjectChangeSetForClone(clone) == null) { |
| modifyVersionField = Boolean.TRUE; |
| } |
| } |
| changes.setShouldModifyVersionField(modifyVersionField); |
| } |
| if (changes.hasChanges() || changes.hasForcedChanges()) { |
| return changes; |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * This is a place holder for reseting the listener on one of the subclasses |
| */ |
| @Override |
| public void clearChanges(Object object, UnitOfWorkImpl uow, ClassDescriptor descriptor, boolean forRefresh) { |
| } |
| |
| /** |
| * INTERNAL: |
| * Create ObjectChangeSet |
| */ |
| public ObjectChangeSet createObjectChangeSet(Object clone, Object backUp, org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet changeSet, boolean isNew, AbstractSession session, ClassDescriptor descriptor) { |
| return this.createObjectChangeSetThroughComparison(clone, backUp, changeSet, isNew, session, descriptor); |
| } |
| |
| /** |
| * INTERNAL: |
| * Create ObjectChangeSet |
| */ |
| @Override |
| public ObjectChangeSet createObjectChangeSetThroughComparison(Object clone, Object backUp, org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet changeSet, boolean isNew, AbstractSession session, ClassDescriptor descriptor) { |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| ObjectChangeSet changes = builder.createObjectChangeSet(clone, changeSet, isNew, true, session); |
| |
| // The following code deals with reads that force changes to the flag associated with optimistic locking. |
| FetchGroup fetchGroup = null; |
| // The flag indicates whether should get fetch group - to avoid doing |
| // that twice. Useful because fetchGroup may be null. |
| boolean shouldGetFetchGroup = true; |
| if ((descriptor.usesOptimisticLocking()) && (changes.getId() != null)) { |
| if (descriptor.hasFetchGroupManager()) { |
| fetchGroup = descriptor.getFetchGroupManager().getObjectFetchGroup(clone); |
| } |
| |
| if (fetchGroup == null || fetchGroup != descriptor.getFetchGroupManager().getIdEntityFetchGroup()) { |
| changes.setOptimisticLockingPolicyAndInitialWriteLockValue(descriptor.getOptimisticLockingPolicy(), session); |
| } |
| |
| // already tried to get the fetch group - no need to do that again. |
| shouldGetFetchGroup = false; |
| } |
| |
| // PERF: Do not create change records for new objects. |
| if (!isNew || descriptor.shouldUseFullChangeSetsForNewObjects() || descriptor.isDescriptorTypeAggregate()) { |
| // PERF: Avoid synchronized enumerator as is concurrency bottleneck. |
| List<DatabaseMapping> mappings = descriptor.getMappings(); |
| int mappingsSize = mappings.size(); |
| if(shouldGetFetchGroup && descriptor.hasFetchGroupManager()) { |
| fetchGroup = descriptor.getFetchGroupManager().getObjectFetchGroup(clone); |
| } |
| for (int index = 0; index < mappingsSize; index++) { |
| DatabaseMapping mapping = mappings.get(index); |
| if ((fetchGroup == null) || fetchGroup.containsAttributeInternal(mapping.getAttributeName())) { |
| changes.addChange(mapping.compareForChange(clone, backUp, changes, session)); |
| } |
| } |
| } |
| |
| return changes; |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to disable changetracking temporarily |
| */ |
| @Override |
| public void dissableEventProcessing(Object changeTracker){ |
| //no-op |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to enable changetracking temporarily |
| */ |
| @Override |
| public void enableEventProcessing(Object changeTracker){ |
| //no-op |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if the Object should be compared, false otherwise. In DeferredChangeDetectionPolicy, |
| * true is always returned since always allow the UnitOfWork to calculate changes. |
| * @param object the object that will be compared |
| * @param unitOfWork the active unitOfWork |
| * @param descriptor the descriptor for the current object |
| */ |
| @Override |
| public boolean shouldCompareExistingObjectForChange(Object object, UnitOfWorkImpl unitOfWork, ClassDescriptor descriptor) { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Build back up clone. Used if clone is new because listener should not be set. |
| */ |
| @Override |
| public Object buildBackupClone(Object clone, ObjectBuilder builder, UnitOfWorkImpl uow) { |
| return builder.buildBackupClone(clone, uow); |
| } |
| |
| /** |
| * INTERNAL: |
| * Assign ChangeListener to an aggregate object |
| */ |
| @Override |
| public void setAggregateChangeListener(Object parent, Object aggregate, UnitOfWorkImpl uow, ClassDescriptor descriptor, String mappingAttribute){ |
| //no-op |
| } |
| |
| /** |
| * INTERNAL: |
| * Set ChangeListener for the clone |
| */ |
| @Override |
| public PropertyChangeListener setChangeListener(Object clone, UnitOfWorkImpl uow, ClassDescriptor descriptor) { |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the ObjectChangeSet on the Listener, initially used for aggregate support |
| */ |
| @Override |
| public void setChangeSetOnListener(ObjectChangeSet objectChangeSet, Object clone){ |
| //no-op |
| } |
| |
| /** |
| * INTERNAL: |
| * Clear changes in the ChangeListener of the clone |
| */ |
| @Override |
| public void updateWithChanges(Object clone, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow, ClassDescriptor descriptor) { |
| if (objectChangeSet == null) { |
| return; |
| } |
| Object backupClone = uow.getCloneMapping().get(clone); |
| if (backupClone != null) { |
| // If new just re-build the backup clone, otherwise use MergeManager (should be a special merge though, not the default constructor like it is...) |
| if (objectChangeSet.isNew()) { |
| uow.getCloneMapping().put(clone, descriptor.getObjectBuilder().buildBackupClone(clone, uow)); |
| } else { |
| MergeManager mergeManager = new MergeManager(uow); |
| mergeManager.setCascadePolicy(MergeManager.NO_CASCADE); |
| descriptor.getObjectBuilder().mergeChangesIntoObject(backupClone, objectChangeSet, clone, mergeManager, mergeManager.getSession()); |
| } |
| } |
| clearChanges(clone, uow, descriptor, false); |
| } |
| |
| /** |
| * INTERNAL: |
| * This may cause a property change event to be raised to a listener in the case that a listener exists. |
| * If there is no listener then this call is a no-op |
| */ |
| @Override |
| public void raiseInternalPropertyChangeEvent(Object source, String propertyName, Object oldValue, Object newValue){ |
| //no-op |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to revert an object within the unit of work |
| * @param cloneMapping may not be the same as what is in the uow |
| */ |
| @Override |
| public void revertChanges(Object clone, ClassDescriptor descriptor, UnitOfWorkImpl uow, Map cloneMapping, boolean forRefresh) { |
| cloneMapping.put(clone, buildBackupClone(clone, descriptor.getObjectBuilder(), uow)); |
| clearChanges(clone, uow, descriptor, forRefresh); |
| } |
| |
| /** |
| * INTERNAL: |
| * initialize the Policy |
| */ |
| @Override |
| public void initialize(AbstractSession session, ClassDescriptor descriptor) { |
| //do nothing |
| } |
| |
| /** |
| * Used to track instances of the change policies without doing an instance of check |
| */ |
| @Override |
| public boolean isDeferredChangeDetectionPolicy(){ |
| return true; |
| } |
| |
| /** |
| * Used to track instances of the change policies without doing an instance of check |
| */ |
| @Override |
| public boolean isObjectChangeTrackingPolicy(){ |
| return false; |
| } |
| |
| /** |
| * Used to track instances of the change policies without doing an instance of check |
| */ |
| @Override |
| public boolean isAttributeChangeTrackingPolicy(){ |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * In cases where a relationship with detached or new entities is merged into itself previous changes may have been recorded for |
| * the detached/new entity that need to be updated. |
| */ |
| @Override |
| public void updateListenerForSelfMerge(ObjectChangeListener listener, ForeignReferenceMapping mapping, Object source, Object target, UnitOfWorkImpl unitOfWork) { |
| //not applicable for this change detection type. |
| } |
| } |