/******************************************************************************* | |
* Copyright (c) 1998, 2013 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 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.descriptors.changetracking; | |
import java.beans.PropertyChangeListener; | |
import java.util.*; | |
import org.eclipse.persistence.internal.sessions.ObjectChangeSet; | |
import org.eclipse.persistence.queries.*; | |
import org.eclipse.persistence.internal.descriptors.*; | |
import org.eclipse.persistence.internal.descriptors.changetracking.ObjectChangeListener; | |
import org.eclipse.persistence.internal.sessions.MergeManager; | |
import org.eclipse.persistence.descriptors.ClassDescriptor; | |
import org.eclipse.persistence.mappings.*; | |
import org.eclipse.persistence.descriptors.DescriptorEvent; | |
import org.eclipse.persistence.descriptors.DescriptorEventManager; | |
import org.eclipse.persistence.exceptions.ValidationException; | |
import org.eclipse.persistence.internal.sessions.AbstractSession; | |
import org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork; | |
import org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet; | |
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; | |
/** | |
* 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. | |
*/ | |
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. | |
*/ | |
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 session the current session | |
* @param descriptor the descriptor for this object | |
* @param shouldRaiseEvent indicates whether PreUpdate event should be risen (usually true) | |
*/ | |
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) { | |
// The query is built for compatibility to old event mechanism. | |
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() && 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 | |
*/ | |
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 | |
*/ | |
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 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 = (DatabaseMapping)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 | |
*/ | |
public void dissableEventProcessing(Object changeTracker){ | |
//no-op | |
} | |
/** | |
* INTERNAL: | |
* This method is used to enable changetracking temporarily | |
*/ | |
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 | |
*/ | |
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. | |
*/ | |
public Object buildBackupClone(Object clone, ObjectBuilder builder, UnitOfWorkImpl uow) { | |
return builder.buildBackupClone(clone, uow); | |
} | |
/** | |
* INTERNAL: | |
* Assign ChangeListener to an aggregate object | |
*/ | |
public void setAggregateChangeListener(Object parent, Object aggregate, UnitOfWorkImpl uow, ClassDescriptor descriptor, String mappingAttribute){ | |
//no-op | |
} | |
/** | |
* INTERNAL: | |
* Set ChangeListener for the clone | |
*/ | |
public PropertyChangeListener setChangeListener(Object clone, UnitOfWorkImpl uow, ClassDescriptor descriptor) { | |
return null; | |
} | |
/** | |
* INTERNAL: | |
* Set the ObjectChangeSet on the Listener, initially used for aggregate support | |
*/ | |
public void setChangeSetOnListener(ObjectChangeSet objectChangeSet, Object clone){ | |
//no-op | |
} | |
/** | |
* INTERNAL: | |
* Clear changes in the ChangeListener of the clone | |
*/ | |
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 | |
*/ | |
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 | |
*/ | |
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 | |
*/ | |
public void initialize(AbstractSession session, ClassDescriptor descriptor) { | |
//do nothing | |
} | |
/** | |
* Used to track instances of the change policies without doing an instance of check | |
*/ | |
public boolean isDeferredChangeDetectionPolicy(){ | |
return true; | |
} | |
/** | |
* Used to track instances of the change policies without doing an instance of check | |
*/ | |
public boolean isObjectChangeTrackingPolicy(){ | |
return false; | |
} | |
/** | |
* Used to track instances of the change policies without doing an instance of check | |
*/ | |
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. | |
*/ | |
public void updateListenerForSelfMerge(ObjectChangeListener listener, ForeignReferenceMapping mapping, Object source, Object target, UnitOfWorkImpl unitOfWork) { | |
//not applicable for this change detection type. | |
} | |
} |