| /* |
| * 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.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.core.helper.CoreClassConstants; |
| 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; |
| |
| /** |
| * <p> |
| * <b>Purpose</b>: Hold the Records of change for a particular instance of an object. |
| * </p><p> |
| * <b>Description</b>: This class uses the Primary Keys of the Object it represents, |
| * and the class. |
| * </p> |
| */ |
| public class ObjectChangeSet implements Serializable, Comparable<ObjectChangeSet>, 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<org.eclipse.persistence.sessions.changesets.ChangeRecord> 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<org.eclipse.persistence.sessions.changesets.ChangeRecord> changes; |
| protected transient Map<String, ChangeRecord> 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<String> 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<org.eclipse.persistence.sessions.changesets.ChangeRecord> 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<String> getChangedAttributeNames() { |
| List<String> 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<org.eclipse.persistence.sessions.changesets.ChangeRecord> 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 = 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) { |
| // 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<org.eclipse.persistence.sessions.changesets.ChangeRecord> 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<ChangeRecord>)(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<ChangeRecord>)(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<String> 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, CoreClassConstants.TIMESTAMP); |
| this.initialWriteLockValue = session.getPlatform(descriptor.getJavaClass()).getConversionManager().convertObject(this.initialWriteLockValue, CoreClassConstants.TIMESTAMP); |
| } 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 |
| */ |
| @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); |
| } |
| |
| } |
| |