All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.internal.sessions.ObjectChangeSet Maven / Gradle / Ivy

There is a newer version: 5.0.0-B03
Show newest version
/*
 * Copyright (c) 1998, 2019 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.internal.sessions;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.FetchGroupManager;
import org.eclipse.persistence.descriptors.TimestampLockingPolicy;
import org.eclipse.persistence.descriptors.VersionLockingPolicy;
import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping;
import org.eclipse.persistence.queries.FetchGroup;
import org.eclipse.persistence.queries.ReadObjectQuery;

/**
 * 

* Purpose: Hold the Records of change for a particular instance of an object. *

* Description: This class uses the Primary Keys of the Object it represents, * and the class. *

*/ public class ObjectChangeSet implements Serializable, Comparable, org.eclipse.persistence.sessions.changesets.ObjectChangeSet { /** Allow change sets to be compared by changes for batching. */ public static class ObjectChangeSetComparator implements Comparator, Serializable { private static final long serialVersionUID = -7902750710186726851L; /** * Determine if the receiver is greater or less than the change set. */ @Override public int compare(Object object1, Object object2) { if (object1 == object2) { return 0; } ObjectChangeSet left = (ObjectChangeSet)object1; ObjectChangeSet right = (ObjectChangeSet)object2; // Sort by changes to keep same SQL together for batching. if ((left.changes != null) && right.changes != null) { int size = left.changes.size(); List otherChanges = right.changes; int otherSize = otherChanges.size(); if (size > otherSize) { return 1; } else if (size < otherSize) { return -1; } for (int index = 0; index < size; index++) { ChangeRecord record = (ChangeRecord)left.changes.get(index); ChangeRecord otherRecord = (ChangeRecord)otherChanges.get(index); int compare = record.getAttribute().compareTo(otherRecord.getAttribute()); if (compare != 0) { return compare; } } } return left.compareTo(right); } } /** This is the collection of changes */ protected List changes; protected transient Map attributesToChanges; protected boolean shouldBeDeleted; protected Object id; protected transient Class classType; protected String className; protected boolean isNew; protected boolean isAggregate; protected Object oldKey; protected Object newKey; protected AbstractRecord protectedForeignKeys; /** This member variable holds the reference to the parent UnitOfWork Change Set **/ protected transient UnitOfWorkChangeSet unitOfWorkChangeSet; /** Used in mergeObjectChanges method for writeLock and initialWriteLock comparison of the merged change sets **/ protected transient OptimisticLockingPolicy optimisticLockingPolicy; protected Object initialWriteLockValue; protected Object writeLockValue; /** Invalid change set shouldn't be merged into object in cache, rather the object should be invalidated **/ protected boolean isInvalid; protected transient Object cloneObject; protected boolean hasVersionChange; /** Contains optimisticReadLockObject corresponding to the clone, non-null indicates forced changes **/ protected Boolean shouldModifyVersionField; /** For CMP only: indicates that the object should be force updated (whether it has OptimisticLocking or not): getCmpPolicy().getForcedUpdate()==true**/ protected transient boolean hasCmpPolicyForcedUpdate; protected transient boolean hasChangesFromCascadeLocking; /** * This is used during attribute level change tracking when a particular * change was detected but that change can not be tracked (ie customer set * entire collection in object). */ protected transient Set deferredSet; /** * Used to store the type of cache synchronization used for this object * This variable is set just before the change set is serialized. */ protected int cacheSynchronizationType; /** PERF: Cache the session cacheKey during the merge to avoid duplicate lookups. */ protected transient CacheKey activeCacheKey; /** Cache the descriptor as it is useful and required in some places. */ protected transient ClassDescriptor descriptor; /** return whether this change set should be recalculated after an event changes the object */ protected transient boolean shouldRecalculateAfterUpdateEvent = true; //This controls how long the thread can wait for other thread to put Entity instance in cache //This is not final to allow a way for the value to be changed without supporting API public static final int MAX_TRIES = 18000; /** * The default constructor. */ public ObjectChangeSet() { } /** * This constructor is used to create an ObjectChangeSet that represents a regular object. */ public ObjectChangeSet(Object primaryKey, ClassDescriptor descriptor, Object cloneObject, UnitOfWorkChangeSet parent, boolean isNew) { this.cacheSynchronizationType = ClassDescriptor.UNDEFINED_OBJECT_CHANGE_BEHAVIOR; this.cloneObject = cloneObject; this.isNew = isNew; this.shouldBeDeleted = false; this.id = primaryKey; this.classType = descriptor.getJavaClass(); this.className = this.classType.getName(); this.descriptor = descriptor; this.cacheSynchronizationType = descriptor.getCachePolicy().getCacheSynchronizationType(); this.unitOfWorkChangeSet = parent; this.isAggregate = false; } public ClassDescriptor getDescriptor() { return descriptor; } public void setDescriptor(ClassDescriptor descriptor) { this.descriptor = descriptor; } /** * INTERNAL: * This method will clear the changerecords from a changeSet */ public void clear(boolean clearKeys) { this.shouldBeDeleted = false; this.changes = null; this.attributesToChanges = null; this.deferredSet = null; if (clearKeys){ this.setOldKey(null); this.setNewKey(null); } } /** * Add the attribute change record. */ public void addChange(ChangeRecord changeRecord) { if (changeRecord == null) { return; } String attributeName = changeRecord.getAttribute(); Map attributeToChanges = getAttributesToChanges(); List changes = getChanges(); ChangeRecord existingChangeRecord = (ChangeRecord)attributeToChanges.get(attributeName); // change tracking may add a change to an existing attribute fix that here. if (existingChangeRecord != null) { changes.remove(existingChangeRecord); } changes.add(changeRecord); attributeToChanges.put(attributeName, changeRecord); dirtyUOWChangeSet(); // now let's do some house keeping. DatabaseMapping mapping = changeRecord.getMapping(); OptimisticLockingPolicy olp = getDescriptor().getOptimisticLockingPolicy(); if (olp != null){ if ((olp.shouldUpdateVersionOnOwnedMappingChange() && mapping.isOwned()) || (olp.shouldUpdateVersionOnMappingChange())){ this.shouldModifyVersionField = true; // must update version field when owned mapping changes } } } /** * INTERNAL: * This method is used during attribute level change tracking when a particular * change was detected but that change can not be tracked (ie customer set * entire collection in object). In this case flag this attribute for * deferred change detection at commit time. */ public void deferredDetectionRequiredOn(String attributeName){ getDeferredSet().add(attributeName); } /** * INTERNAL: * Convenience method used to query this change set after it has been sent by * cache synchronization. * @return true if this change set should contain all change information, false if only * the identity information should be available. */ public boolean containsChangesFromSynchronization() { return ((cacheSynchronizationType == ClassDescriptor.SEND_NEW_OBJECTS_WITH_CHANGES) || (cacheSynchronizationType == ClassDescriptor.SEND_OBJECT_CHANGES)); } /** * Ensure change sets with the same primary key are equal. */ @Override public boolean equals(Object object) { if (object instanceof ObjectChangeSet) { return equals((ObjectChangeSet)object); } return false; } /** * Ensure change sets with the same primary key are equal. */ public boolean equals(ObjectChangeSet objectChange) { if (this == objectChange) { return true; } else if (this.id == null) { //new objects are compared based on identity return false; } return (this.id.equals(objectChange.id)); } /** * Determine if the receiver is greater or less than the change set. */ @Override public int compareTo(ObjectChangeSet changeSet) { if (this == changeSet) { return 0; } if (this.id == null) { if (changeSet.id != null) { return -1; } else { return 0; } } else if (changeSet.id == null) { return 1; } try { return ((Comparable)this.id).compareTo(changeSet.id); } catch (Exception exception) { return 0; } } /** * INTERNAL: * stores the change records indexed by the attribute names */ public Map getAttributesToChanges() { if (this.attributesToChanges == null) { this.attributesToChanges = new HashMap(); } return this.attributesToChanges; } /** * INTERNAL: * returns the change record for the specified attribute name */ @Override public org.eclipse.persistence.sessions.changesets.ChangeRecord getChangesForAttributeNamed(String attributeName) { return (ChangeRecord)this.getAttributesToChanges().get(attributeName); } /** * ADVANCED: * This method will return a collection of the attributes changed in the object. */ @Override public List getChangedAttributeNames() { List names = new ArrayList<>(); for (org.eclipse.persistence.sessions.changesets.ChangeRecord changeRecord : getChanges()) { names.add(changeRecord.getAttribute()); } return names; } /** * INTERNAL: * This method returns a reference to the collection of changes within this changeSet. */ @Override public List getChanges() { if (this.changes == null) { this.changes = new ArrayList<>(); } return changes; } /** * INTERNAL: * This method returns the class type that this changeSet represents. * The class type must be initialized, before this method is called. * @return java.lang.Class or null if the class type isn't initialized. */ public Class getClassType() { return classType; } /** * ADVANCE: * This method returns the class type that this changeSet Represents. * This requires the session to reload the class on serialization. */ @Override public Class getClassType(org.eclipse.persistence.sessions.Session session) { if (classType == null) { classType = (Class) session.getDatasourcePlatform().getConversionManager().convertObject(getClassName(), ClassConstants.CLASS); } return classType; } /** * ADVANCE: * This method returns the class type that this changeSet Represents. * The class type should be used if the class is desired. */ @Override public String getClassName() { return className; } /** * INTERNAL: * This method is used to return the initial lock value of the object this changeSet represents. */ public Object getInitialWriteLockValue() { return initialWriteLockValue; } /** * This method returns the key value that this object was stored under in it's * Respective hashmap. */ @Override public Object getOldKey() { return this.oldKey; } /** * This method returns the key value that this object will be stored under in it's * Respective hashmap. */ @Override public Object getNewKey() { return this.newKey; } /** * ADVANCED: * This method returns the primary key for the object that this change set represents. */ @Override public Object getId() { return this.id; } public Object getOldValue() { AbstractSession session = null; if(this.unitOfWorkChangeSet != null) { session = this.unitOfWorkChangeSet.getSession(); } return getOldValue(session); } public Object getOldValue(AbstractSession session) { if (this.isNew) { return null; } if (this.changes == null || this.changes.isEmpty()) { // object has not changed return this.cloneObject; } else { if(this.cloneObject != null && session != null) { Object oldValue = this.descriptor.getObjectBuilder().buildNewInstance(); FetchGroup fetchGroup = null; FetchGroupManager fetchGroupManager = this.descriptor.getFetchGroupManager(); if(fetchGroupManager != null) { fetchGroup = fetchGroupManager.getObjectFetchGroup(this.cloneObject); } for(DatabaseMapping mapping : this.descriptor.getMappings()) { String attributeName = mapping.getAttributeName(); if(fetchGroup == null || fetchGroup.containsAttributeInternal(attributeName)) { ChangeRecord changeRecord = (ChangeRecord)getChangesForAttributeNamed(attributeName); if(changeRecord != null) { mapping.setRealAttributeValueInObject(oldValue, changeRecord.getOldValue()); } else { mapping.setAttributeValueInObject(oldValue, mapping.getAttributeValueFromObject(this.cloneObject)); } } } return oldValue; } } return null; } public int getSynchronizationType() { return cacheSynchronizationType; } /** * INTERNAL: * This method is used to return the complex object specified within the change record. * The object is collected from the session which, in this case, is the unit of work. * The object's changed attributes will be merged and added to the identity map. */ public Object getTargetVersionOfSourceObject(MergeManager mergeManager, AbstractSession session) { return getTargetVersionOfSourceObject(mergeManager, session, false); } /** * INTERNAL: * This method is used to return the complex object specified within the change record. * The object is collected from the session which, in this case, is the unit of work. * The object's changed attributes will be merged and added to the identity map * @param shouldRead boolean if the object can not be found should it be read in from the database. */ public Object getTargetVersionOfSourceObject(MergeManager mergeManager, AbstractSession targetSession, boolean shouldRead) { Object attributeValue = null; ClassDescriptor descriptor = getDescriptor(); if (descriptor == null) { descriptor = targetSession.getDescriptor(getClassType(targetSession)); } if (descriptor != null) { if (mergeManager.getSession().isUnitOfWork()) { // The unit of works will have a copy or a new instance must be made if (((UnitOfWorkImpl)mergeManager.getSession()).getLifecycle() == UnitOfWorkImpl.MergePending) { // We are merging the unit of work into the original. attributeValue = getObjectForMerge(mergeManager, targetSession, getId(), descriptor); if (attributeValue == null){ // Bug 502085 UnitOfWorkImpl uow = (UnitOfWorkImpl)mergeManager.getSession(); if (this.isNew()) { attributeValue = uow.getOriginalVersionOfObject(getUnitOfWorkClone()); } else { attributeValue = uow.getOriginalVersionOfObjectOrNull(getUnitOfWorkClone(), this, descriptor, targetSession); } } } else { // We are merging something else within the unit of work. // this is most likely because we are updating a backup clone and can retrieve // the working clone as the result. attributeValue = getUnitOfWorkClone(); } } else { // It is not a unitOfWork so we must be merging into a distributed cache. attributeValue = getObjectForMerge(mergeManager, targetSession, getId(), descriptor); } if ((attributeValue == null) && (shouldRead)) { // If the cache does not have a copy and I should read it from the database // Then load the object if possible ReadObjectQuery query = new ReadObjectQuery(); query.setShouldUseWrapperPolicy(false); query.setReferenceClass(getClassType(targetSession)); query.setSelectionId(getId()); attributeValue = targetSession.executeQuery(query); } } return attributeValue; } /** * INTERNAL: * For use within the distributed merge process, this method will get an object from the shared * cache using a readlock. If a readlock is unavailable then the merge manager will be * transitioned to deferred locks and a deferred lock will be used. */ protected Object getObjectForMerge(MergeManager mergeManager, AbstractSession session, Object primaryKey, ClassDescriptor descriptor) { Object domainObject = null; if (primaryKey == null) { this.activeCacheKey = null; return null; } CacheKey cacheKey = session.getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, descriptor.getJavaClass(), descriptor, true); if (cacheKey != null) { if (cacheKey.acquireReadLockNoWait()) { domainObject = cacheKey.getObject(); cacheKey.releaseReadLock(); } else { if (!mergeManager.isTransitionedToDeferredLocks()) { session.getIdentityMapAccessorInstance().getWriteLockManager().transitionToDeferredLocks(mergeManager); } cacheKey.acquireDeferredLock(); domainObject = cacheKey.getObject(); int tries = 0; while (domainObject == null) { ++tries; if (tries > MAX_TRIES){ session.getParent().log(SessionLog.SEVERE, SessionLog.CACHE, "entity_not_available_during_merge", new Object[]{descriptor.getJavaClassName(), cacheKey.getKey(), Thread.currentThread().getName(), cacheKey.getActiveThread()}); break; } synchronized (cacheKey) { if (cacheKey.isAcquired()) { try { cacheKey.wait(10); } catch (InterruptedException e) { //ignore and return } } domainObject = cacheKey.getObject(); } } cacheKey.releaseDeferredLock(); } } else { domainObject = mergeManager.registerExistingObjectOfReadOnlyClassInNestedTransaction(getUnitOfWorkClone(), descriptor, session); // There is no need to get the cache key in this case because UOW is performing // a nested UOW merge, and no locking occurs. } // Set activeCacheKey. this.activeCacheKey = cacheKey; return domainObject; } /** * INTERNAL: * Returns the UnitOfWork Clone that this ChangeSet was built for. */ public Object getUnitOfWorkClone() { return this.cloneObject; } /** * INTERNAL: * Sets the UnitOfWork Clone that this ChangeSet was built for. */ public void setUnitOfWorkClone(Object cloneObject) { this.cloneObject = cloneObject; } /** * ADVANCED: * This method is used to return the parent UnitOfWorkChangeSet. */ @Override public org.eclipse.persistence.sessions.changesets.UnitOfWorkChangeSet getUOWChangeSet() { return unitOfWorkChangeSet; } /** * INTERNAL: * This method is used to return the lock value of the object this changeSet represents. */ @Override public Object getWriteLockValue() { return writeLockValue; } /** * ADVANCED: * This method will return true if the specified attribute has been changed. * @param attributeName the name of the attribute to search for. */ @Override public boolean hasChangeFor(String attributeName) { for (org.eclipse.persistence.sessions.changesets.ChangeRecord changeRecord : getChanges()) { if (changeRecord.getAttribute().equals(attributeName)) { return true; } } return false; } /** * ADVANCED: * Returns true if this particular changeSet has changes. */ @Override public boolean hasChanges() { // a change set must also be considered dirty if only the version number has been updated // and the version is not a mapped field. This is required to propagate the change // set via cache sync. to avoid opt. lock exceptions on the remote servers. return this.isNew || this.hasVersionChange || ((this.changes != null) && (!this.changes.isEmpty())); } /** * INTERNAL: * Returns true if this particular changeSet has forced SQL changes. This is true whenever * CMPPolicy.getForceUpdate() == true or if the object has been marked for opt. read * lock (uow.forceUpdateToVersionField). Kept separate from 'hasChanges' because we don't * want to merge or cache sync. a change set that has no 'real' changes. */ public boolean hasForcedChanges() { return this.shouldModifyVersionField != null || this.hasCmpPolicyForcedUpdate; } /** * INTERNAL: * Holds a Boolean indicating whether version field should be modified. * This Boolean is set by forcedUpdate into uow.getOptimisticReadLockObjects() * for the clone object and copied here (so don't need to search for it again * in uow.getOptimisticReadLockObjects()). */ public void setShouldModifyVersionField(Boolean shouldModifyVersionField) { this.shouldModifyVersionField = shouldModifyVersionField; if(shouldModifyVersionField != null && shouldModifyVersionField.booleanValue()) { // mark the version number as 'dirty' // Note that at this point there is no newWriteLockValue - it will be set later. // This flag is set to indicate that the change set WILL have changes. this.hasVersionChange = true; } } /** * INTERNAL: * Holds a Boolean indicating whether version field should be modified. */ public Boolean shouldModifyVersionField() { return this.shouldModifyVersionField; } /** * INTERNAL: */ public void setHasCmpPolicyForcedUpdate(boolean hasCmpPolicyForcedUpdate) { this.hasCmpPolicyForcedUpdate = hasCmpPolicyForcedUpdate; } /** * INTERNAL: */ public boolean hasCmpPolicyForcedUpdate() { return this.hasCmpPolicyForcedUpdate; } /** * INTERNAL: * Returns true if this particular changeSet has forced SQL changes because * of a cascade optimistic locking policy. */ public boolean hasForcedChangesFromCascadeLocking() { return this.hasChangesFromCascadeLocking; } /** * INTERNAL: * Used by calculateChanges to mark this ObjectChangeSet as having to be * flushed to the db stemming from a cascade optimistic locking policy. */ public void setHasForcedChangesFromCascadeLocking(boolean newValue) { this.setShouldModifyVersionField(Boolean.TRUE); this.hasChangesFromCascadeLocking = newValue; } /** * This method overrides the hashcode method. If this set has a cacheKey then return the hashcode of the * cache key, otherwise return the identity hashcode of this object. */ @Override public int hashCode() { if (getId() == null) { //new objects are compared based on identity return System.identityHashCode(this); } return getId().hashCode(); } /** * INTERNAL: * Returns true if this particular changeSet has a Key. */ public boolean hasKeys() { return (this.newKey != null) || (this.oldKey != null); } /** * INTERNAL: * Used to determine if the object change set represents an aggregate object. */ public boolean isAggregate() { return isAggregate; } /** * ADVANCED: * Returns true if this ObjectChangeSet represents a new object. */ @Override public boolean isNew() { return isNew; } /** * INTERNAL: * Indicates whether the change set is invalid. */ public boolean isInvalid() { return isInvalid; } /** * INTERNAL: * This method will be used to merge changes from a supplied ObjectChangeSet * into this changeSet. */ public void mergeObjectChanges(ObjectChangeSet changeSetToMergeFrom, UnitOfWorkChangeSet mergeToChangeSet, UnitOfWorkChangeSet mergeFromChangeSet) { if (this == changeSetToMergeFrom || this.isInvalid()) { return; } if(changeSetToMergeFrom.optimisticLockingPolicy != null) { // optimisticLockingPolicy != null guarantees initialWriteLockValue != null if(this.optimisticLockingPolicy == null) { this.optimisticLockingPolicy = changeSetToMergeFrom.optimisticLockingPolicy; this.initialWriteLockValue = changeSetToMergeFrom.initialWriteLockValue; this.writeLockValue = changeSetToMergeFrom.writeLockValue; } else { // optimisticLockingPolicy != null guarantees initialWriteLockValue != null Object writeLockValueToCompare = this.writeLockValue; if(writeLockValueToCompare == null) { writeLockValueToCompare = this.initialWriteLockValue; } // In this merge initialWriteLockValue of this changeSet differs from // writeLockValue of the changeSetToMergeFrom into which the merge was performed. // Example: // Original registered with version 1, the clone changed to version 2, uow.writeChanges is called: // the corresponding "this" changeSet has initialWriteLockValue = 1 and writeLockValue = 2; // custom update performed next changing the version of the object in the db to 3; // the clone is refreshed in the uow - now it's version is 3; // the cloned is changed to version 4, uow.commit is called: // the corresponding changeSetToMergeFrom has initialWriteLockValue = 3 and writeLockValue = 4. // This change set should be invalidated - the custom update would not be reflected after merge, // therefore no merge into cache should be performed but rather the object in the cache should be invalidated. if(this.optimisticLockingPolicy.compareWriteLockValues(writeLockValueToCompare, changeSetToMergeFrom.initialWriteLockValue) != 0) { this.isInvalid = true; return; } // Don't blindly overrite a write lock value with null. A // consecutive change set may not have caused a version change, // therefore the write lock value will be null in this case. // E.g. Attribute change tracking does not discover a change // across a relational mapping (unless a cascaded optimistic // locking policy is used). if (changeSetToMergeFrom.writeLockValue != null) { this.writeLockValue = changeSetToMergeFrom.writeLockValue; } } } List changesToMerge = changeSetToMergeFrom.getChanges(); int size = changesToMerge.size(); for (int index = 0; index < size; ++index) { ChangeRecord record = (ChangeRecord)changesToMerge.get(index); ChangeRecord thisRecord = (ChangeRecord) getChangesForAttributeNamed(record.getAttribute()); if (thisRecord == null) { record.updateReferences(mergeToChangeSet, mergeFromChangeSet); record.setOwner(this); this.addChange(record); } else { thisRecord.mergeRecord(record, mergeToChangeSet, mergeFromChangeSet); } } this.shouldBeDeleted = changeSetToMergeFrom.shouldBeDeleted; this.setOldKey(changeSetToMergeFrom.oldKey); this.setNewKey(changeSetToMergeFrom.newKey); this.hasVersionChange = changeSetToMergeFrom.hasVersionChange; this.shouldModifyVersionField = changeSetToMergeFrom.shouldModifyVersionField; this.hasCmpPolicyForcedUpdate = changeSetToMergeFrom.hasCmpPolicyForcedUpdate; this.hasChangesFromCascadeLocking = changeSetToMergeFrom.hasChangesFromCascadeLocking; this.deferredSet = changeSetToMergeFrom.deferredSet; } /** * INTERNAL: * Helper method used by readObject to read a completely serialized change set from * the stream. */ public void readCompleteChangeSet(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException { readIdentityInformation(stream); // bug 3526981 - avoid side effects of setter methods by directly assigning variables // still calling setOldKey to avoid duplicating the code in that method this.changes = (List)stream.readObject(); this.oldKey = stream.readObject(); this.newKey = stream.readObject(); this.protectedForeignKeys = (AbstractRecord)stream.readObject(); } /** * INTERNAL: * Helper method used by readObject to read just the information about object identity * from a serialized stream. */ public void readIdentityInformation(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException { // bug 3526981 - avoid side effects of setter methods by directly assigning variables this.id = stream.readObject(); this.className = (String)stream.readObject(); this.writeLockValue = stream.readObject(); this.initialWriteLockValue = stream.readObject(); } /** * INTERNAL: * Override the default serialization. Object Change Sets will be serialized differently * depending on the type of cache synchronization they use. */ private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException { int cacheSyncType = stream.read(); this.cacheSynchronizationType = cacheSyncType; // The boolean variables have been assembled into a byte. // Extract them here this.shouldBeDeleted = stream.readBoolean(); this.isInvalid = stream.readBoolean(); this.isNew = stream.readBoolean(); this.isAggregate = stream.readBoolean(); this.shouldModifyVersionField = (Boolean)stream.readObject(); this.hasVersionChange = stream.readBoolean(); // Only the identity information is sent with a number of cache synchronization types // Here we decide what to read. if (this.shouldBeDeleted || (cacheSyncType == ClassDescriptor.DO_NOT_SEND_CHANGES) || (cacheSyncType == ClassDescriptor.INVALIDATE_CHANGED_OBJECTS)) { readIdentityInformation(stream); } else { readCompleteChangeSet(stream); } } /** * Set the id of the object for this change set. */ public void setId(Object id) { this.id = id; } /** * Set the changes. */ public void setChanges(List changesList) { this.changes = changesList; updateUOWChangeSet(); } /** * Set the class type. */ public void setClassType(Class newValue) { this.classType = newValue; } /** * INTERNAL: * Set the class name. The name is used for serialization with cache coordination. */ public void setClassName(String newValue) { this.className = newValue; } /** * INTERNAL: * Set if this object change Set represents an aggregate * @param isAggregate boolean true if the ChangeSet represents an aggregate */ public void setIsAggregate(boolean isAggregate) { this.isAggregate = isAggregate; } /** * INTERNAL: * Set whether this ObjectChanges represents a new Object * @param newIsNew boolean true if this ChangeSet represents a new object */ public void setIsNew(boolean newIsNew) { isNew = newIsNew; } /** * This method is used to set the value that this object was stored under in its respected * map collection */ public void setOldKey(Object key) { //may be merging changeSets lets make sure that we can remove based on the //old key when we finally merge. if ((key == null) || (this.oldKey == null)) { this.oldKey = key; } } /** * This method is used to set the value that this object will be stored under in its respected * map collection */ public void setNewKey(Object key) { this.newKey = key; } /** * This method was created in VisualAge. * @param newValue boolean */ public void setShouldBeDeleted(boolean newValue) { this.shouldBeDeleted = newValue; } public void setSynchronizationType(int type) { cacheSynchronizationType = type; } /** * INTERNAL: * Used to set the parent change Set. */ public void setUOWChangeSet(UnitOfWorkChangeSet newUnitOfWorkChangeSet) { unitOfWorkChangeSet = newUnitOfWorkChangeSet; } /** * INTERNAL: * This method should ONLY be used to set the initial writeLock value for * an ObjectChangeSet when it is first built. */ public void setOptimisticLockingPolicyAndInitialWriteLockValue(OptimisticLockingPolicy optimisticLockingPolicy, AbstractSession session) { // ignore optimistic locking policy if it can't compare lock values (like FieldsLockingPolicy). if(optimisticLockingPolicy.supportsWriteLockValuesComparison()) { this.optimisticLockingPolicy = optimisticLockingPolicy; this.initialWriteLockValue = optimisticLockingPolicy.getWriteLockValue(cloneObject, getId(), session); } } /** * ADVANCED: * This method is used to set the writeLock value for an ObjectChangeSet * Any changes to the write lock value * should to through setWriteLockValue(Object obj) so that the change set is * marked as being dirty. */ public void setWriteLockValue(java.lang.Object newWriteLockValue) { this.writeLockValue = newWriteLockValue; // mark the version number as 'dirty' this.hasVersionChange = true; updateUOWChangeSet(); } /** * ADVANCED: * This method is used to set the initial writeLock value for an ObjectChangeSet. * The initial value will only be set once, and can not be overwritten. */ public void setInitialWriteLockValue(Object initialWriteLockValue) { if (this.initialWriteLockValue == null) { this.initialWriteLockValue = initialWriteLockValue; } } /** * Mark change set for a deleted object. */ public boolean shouldBeDeleted() { return shouldBeDeleted; } @Override public String toString() { return this.getClass().getSimpleName() + "(" + hashCode() + ", " + this.getClassName() + ")" + getChanges().toString(); } /** * INTERNAL: * Used to update a changeRecord that is stored in the CHangeSet with a new value. */ public void updateChangeRecordForAttribute(String attributeName, Object value) { ChangeRecord changeRecord = (ChangeRecord)getChangesForAttributeNamed(attributeName); if (changeRecord != null) { changeRecord.updateChangeRecordWithNewValue(value); } } /** * ADVANCED: * Used to update a changeRecord that is stored in the CHangeSet with a new value. * Used when the new value is a mapped object. */ public void updateChangeRecordForAttributeWithMappedObject(String attributeName, Object value, AbstractSession session) { ObjectChangeSet referenceChangeSet = (ObjectChangeSet)this.getUOWChangeSet().getObjectChangeSetForClone(value); if (referenceChangeSet == null) { ClassDescriptor descriptor = session.getDescriptor(value.getClass()); if (descriptor != null) { referenceChangeSet = descriptor.getObjectBuilder().createObjectChangeSet(value, (UnitOfWorkChangeSet)this.getUOWChangeSet(), false, session); } } updateChangeRecordForAttribute(attributeName, referenceChangeSet); } /** * INTERNAL: * Used to update a changeRecord that is stored in the CHangeSet with a new value. */ public void updateChangeRecordForAttribute(DatabaseMapping mapping, Object value, AbstractSession session, Object oldValue) { String attributeName = mapping.getAttributeName(); ChangeRecord changeRecord = (ChangeRecord)getChangesForAttributeNamed(attributeName); // bug 2641228 always ensure that we convert the value to the correct type if (mapping.isDirectToFieldMapping()) { value = ((AbstractDirectMapping)mapping).getObjectValue(value, session); } if (changeRecord != null) { changeRecord.updateChangeRecordWithNewValue(value); } else if (mapping.isDirectToFieldMapping()) { // If it is direct to field then this is most likely the result of a forced update and // we will need to merge this object. changeRecord = new DirectToFieldChangeRecord(this); changeRecord.setAttribute(attributeName); changeRecord.setMapping(mapping); ((DirectToFieldChangeRecord)changeRecord).setNewValue(value); ((DirectToFieldChangeRecord)changeRecord).setOldValue(oldValue); this.addChange(changeRecord); } } /** * INTERNAL: * This method will be used when merging changesets into other changesets. * It will fix references within a changeSet so that it's records point to * changesets within this UOWChangeSet. */ public void updateReferences(UnitOfWorkChangeSet localChangeSet, UnitOfWorkChangeSet mergingChangeSet) { int size = getChanges().size(); for (int index = 0; index < size; ++index) { ChangeRecord record = (ChangeRecord)getChanges().get(index); record.updateReferences(localChangeSet, mergingChangeSet); record.setOwner(this); } } /** * INTERNAL: * Override the default serialization since different parts of an ObjectChangeSet will * be serialized depending on the type of CacheSynchronizationType */ private void writeObject(java.io.ObjectOutputStream stream) throws java.io.IOException { stream.write(this.cacheSynchronizationType); stream.writeBoolean(this.shouldBeDeleted); stream.writeBoolean(this.isInvalid); stream.writeBoolean(this.isNew); stream.writeBoolean(this.isAggregate); stream.writeObject(this.shouldModifyVersionField); stream.writeBoolean(this.hasVersionChange); if (this.shouldBeDeleted || (this.cacheSynchronizationType == ClassDescriptor.DO_NOT_SEND_CHANGES) || (this.cacheSynchronizationType == ClassDescriptor.INVALIDATE_CHANGED_OBJECTS)) { writeIdentityInformation(stream); } else { writeCompleteChangeSet(stream); } } /** * INTERNAL: * Helper method to writeObject. Write only the information necessary to identify this * ObjectChangeSet to the stream */ public void writeIdentityInformation(java.io.ObjectOutputStream stream) throws java.io.IOException { stream.writeObject(this.id); stream.writeObject(this.className); stream.writeObject(this.writeLockValue); stream.writeObject(this.initialWriteLockValue); } /** * INTERNAL: * Helper method to readObject. Completely write this ObjectChangeSet to the stream */ public void writeCompleteChangeSet(java.io.ObjectOutputStream stream) throws java.io.IOException { writeIdentityInformation(stream); stream.writeObject(this.changes); stream.writeObject(this.oldKey); stream.writeObject(this.newKey); stream.writeObject(this.protectedForeignKeys); } /** * INTERNAL: * Reset the change set's transient variables after serialization. */ public void postSerialize(Object clone, UnitOfWorkChangeSet uowChangeSet, AbstractSession session) { this.unitOfWorkChangeSet = uowChangeSet; // Clone is null for recursive aggregate call, (clone will be set from root call, // but descriptor and mapping needs to be set here. if (clone != null) { this.cloneObject = clone; if (this.descriptor == null) { this.descriptor = session.getDescriptor(clone); this.classType = clone.getClass(); } } if ((this.attributesToChanges == null) && (this.changes != null)) { for (ChangeRecord change : (List)(List)this.changes) { getAttributesToChanges().put(change.getAttribute(), change); } } // Aggregates should only be cascaded to, as they need the correct descriptor from the mapping. if ((this.changes != null) && (this.descriptor != null) && ((clone == null) || !this.descriptor.isAggregateDescriptor())) { for (ChangeRecord change : (List)(List)this.changes) { DatabaseMapping mapping = this.descriptor.getObjectBuilder().getMappingForAttributeName(change.getAttribute()); change.setMapping(mapping); if ((mapping != null) && mapping.isAggregateObjectMapping()) { AggregateChangeRecord aggregate = (AggregateChangeRecord)change; ObjectChangeSet aggregateCacheSet = (ObjectChangeSet)aggregate.getChangedObject(); if (aggregateCacheSet != null) { aggregateCacheSet.setDescriptor(mapping.getReferenceDescriptor()); aggregateCacheSet.postSerialize(null, uowChangeSet, session); } } } } } /** * This set contains the list of attributes that must be calculated at commit time. */ public Set getDeferredSet() { if (deferredSet == null){ this.deferredSet = new HashSet<>(); } return deferredSet; } /** * Check to see if there are any attributes that must be calculated at commit time. */ public boolean hasDeferredAttributes() { return ! (deferredSet == null || this.deferredSet.isEmpty()); } protected void dirtyUOWChangeSet() { // PERF: Set the unit of work change set to dirty avoid unnecessary message sends. UnitOfWorkChangeSet unitOfWorkChangeSet = (UnitOfWorkChangeSet)getUOWChangeSet(); if (unitOfWorkChangeSet != null) { unitOfWorkChangeSet.setHasChanges(true); } } protected void updateUOWChangeSet() { // needed to explicitly mark parent uow as having changes. This is needed in the // case of Optimistic read locking and ForceUpdate. In these scenarios, the object // change set can be modified to contain 'real' changes after the uow change set has // computed its 'hasChanges' flag. If not done, the change set will not be merged. if (getUOWChangeSet() != null) { ((org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet)this.getUOWChangeSet()).setHasChanges(this.hasChanges()); } } /** * Rebuild writeLockValue to the expected type from user format i.e XML change set has all values as String. */ protected void rebuildWriteLockValueFromUserFormat(ClassDescriptor descriptor, AbstractSession session) { if (descriptor.getOptimisticLockingPolicy() instanceof TimestampLockingPolicy) { this.writeLockValue = session.getPlatform(descriptor.getJavaClass()).getConversionManager().convertObject(this.writeLockValue, ClassConstants.JavaSqlTimestamp_Class); this.initialWriteLockValue = session.getPlatform(descriptor.getJavaClass()).getConversionManager().convertObject(this.initialWriteLockValue, ClassConstants.JavaSqlTimestamp_Class); } else if (descriptor.getOptimisticLockingPolicy() instanceof VersionLockingPolicy) { this.writeLockValue = session.getPlatform(descriptor.getJavaClass()).getConversionManager().convertObject(this.writeLockValue, ClassConstants.BIGDECIMAL); this.initialWriteLockValue = session.getPlatform(descriptor.getJavaClass()).getConversionManager().convertObject(this.initialWriteLockValue, ClassConstants.BIGDECIMAL); } } /** * INTERNAL: * Remove the change. * Used by the event mechanism to reset changes after client has updated the object within an event. */ public void removeChange(String attributeName){ Object record = getChangesForAttributeNamed(attributeName); if (record != null) { getChanges().remove(record); this.attributesToChanges.remove(attributeName); } } /** * Remove object represent this change set from identity map. If change set is in XML format, rebuild pk to the correct class type from String */ protected void removeFromIdentityMap(AbstractSession session) { session.getIdentityMapAccessor().removeFromIdentityMap(getId(), getClassType(session)); } /** * INTERNAL: * Indicates whether the object in session cache should be invalidated. * @param original Object is from session's cache into which the changes are about to be merged, non null. * @param session AbstractSession into which the changes are about to be merged; */ public boolean shouldInvalidateObject(Object original, AbstractSession session) { // Either no optimistic locking or no version change. if (optimisticLockingPolicy == null) { return false; } if (session.isRemoteSession()){ //remote unit of work not supported as version values in UOW will be updated //when the committed UOW is received on the client. That updated value will be //set in the UOW cache when the changeset is calculated giving the changeset the //incorrect initialWriteLockValue value //version number comparison will still be completed later. return false; } if(isInvalid()) { return true; } Object originalWriteLockValue = optimisticLockingPolicy.getWriteLockValue(original, getId(), session); // initialWriteLockValue and originalWriteLockValue are not equal. // Example: // original registered in uow with version 1 (originalWriteLockValue); // uow.beginEarlyTransaction(); // custom update run through the uow changes the version on the object in the db to 2; // the clone is refreshed - now it has version 2; // on uow.commit or uow.writeChanges changeSet is created with initialWriteLockValue = 2; // The original in the cache should be invalidated - the custom update would not be reflected after merge. if (this.initialWriteLockValue == null){ if (this.hasChanges()){ return true; // no initial version was available but we will be merging changes with unknown version force invalidation }else{ return false; // don't invalidate as we are not merging anything anyway } } if (originalWriteLockValue != null && optimisticLockingPolicy.compareWriteLockValues(initialWriteLockValue, originalWriteLockValue) != 0) { return true; } else { return false; } } /** * INTERNAL: * PERF: Return the session cache-key, cached during the merge. */ public CacheKey getActiveCacheKey() { return activeCacheKey; } /** * INTERNAL: * PERF: Set the session cache-key, cached during the merge. */ public void setActiveCacheKey(CacheKey activeCacheKey) { this.activeCacheKey = activeCacheKey; } /** * ADVANCED * Returns true if this ObjectChangeSet should be recalculated after changes in event * @return */ @Override public boolean shouldRecalculateAfterUpdateEvent() { return shouldRecalculateAfterUpdateEvent; } /** * ADVANCED * Set whether this ObjectChangeSet should be recalculated after changes in event */ @Override public void setShouldRecalculateAfterUpdateEvent(boolean shouldRecalculateAfterUpdateEvent) { this.shouldRecalculateAfterUpdateEvent = shouldRecalculateAfterUpdateEvent; } public boolean hasVersionChange() { return hasVersionChange; } public void setHasVersionChange(boolean hasVersionChange) { this.hasVersionChange = hasVersionChange; } public int getCacheSynchronizationType() { return cacheSynchronizationType; } public void setCacheSynchronizationType(int cacheSynchronizationType) { this.cacheSynchronizationType = cacheSynchronizationType; } public void setIsInvalid(boolean isInvalid) { this.isInvalid = isInvalid; } public AbstractRecord getProtectedForeignKeys() { return this.protectedForeignKeys; } public void setProtectedForeignKeys(AbstractRecord protectedForeignKeys) { this.protectedForeignKeys = protectedForeignKeys; } public boolean hasProtectedForeignKeys() { return (this.protectedForeignKeys != null) && (this.protectedForeignKeys.size() > 0); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy