/******************************************************************************* | |
* Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved. | |
* This program and the accompanying materials are made available under the | |
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 | |
* which accompanies this distribution. | |
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html | |
* and the Eclipse Distribution License is available at | |
* http://www.eclipse.org/org/documents/edl-v10.php. | |
* | |
* Contributors: | |
* Oracle - initial API and implementation from Oracle TopLink | |
******************************************************************************/ | |
package org.eclipse.persistence.descriptors; | |
import java.io.*; | |
import java.util.*; | |
import org.eclipse.persistence.mappings.*; | |
import org.eclipse.persistence.internal.helper.*; | |
import org.eclipse.persistence.queries.*; | |
import org.eclipse.persistence.expressions.*; | |
import org.eclipse.persistence.exceptions.*; | |
import org.eclipse.persistence.internal.descriptors.ObjectBuilder; | |
import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy; | |
import org.eclipse.persistence.internal.identitymaps.CacheKey; | |
import org.eclipse.persistence.internal.sessions.AbstractRecord; | |
import org.eclipse.persistence.internal.sessions.AbstractSession; | |
import org.eclipse.persistence.internal.sessions.DirectToFieldChangeRecord; | |
import org.eclipse.persistence.internal.sessions.ObjectChangeSet; | |
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; | |
import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping; | |
/** | |
* <p><b>Purpose</b>: Used to allow a single version number to be used for optimistic locking. | |
* | |
* @since TOPLink/Java 2.0 | |
*/ | |
public class VersionLockingPolicy implements OptimisticLockingPolicy, Serializable { | |
protected DatabaseField writeLockField; | |
protected boolean isCascaded; | |
protected int lockValueStored; | |
protected ClassDescriptor descriptor; | |
protected transient Expression cachedExpression; | |
public final static int IN_CACHE = 1; | |
public final static int IN_OBJECT = 2; | |
/** PERF: Cache the lock mapping if mapped with a direct mapping. */ | |
protected AbstractDirectMapping lockMapping; | |
protected LockOnChange lockOnChangeMode; | |
/** | |
* PUBLIC: | |
* Create a new VersionLockingPolicy. Defaults to | |
* storing the lock value in the cache. | |
*/ | |
public VersionLockingPolicy() { | |
super(); | |
storeInCache(); | |
} | |
/** | |
* PUBLIC: | |
* Create a new VersionLockingPolicy. Defaults to | |
* storing the lock value in the cache. | |
* @param fieldName specifies the field name for the write | |
* lock field. | |
*/ | |
public VersionLockingPolicy(String fieldName) { | |
this(new DatabaseField(fieldName)); | |
} | |
/** | |
* PUBLIC: | |
* Create a new VersionLockingPolicy. Defaults to | |
* storing the lock value in the cache. | |
* @param field the write lock field. | |
*/ | |
public VersionLockingPolicy(DatabaseField field) { | |
this(); | |
setWriteLockField(field); | |
} | |
/** | |
* INTERNAL: | |
* Add update fields for template row. | |
* These are any unmapped fields required to write in an update. | |
*/ | |
public void addLockFieldsToUpdateRow(AbstractRecord databaseRow, AbstractSession session) { | |
if (isStoredInCache()) { | |
databaseRow.put(getWriteLockField(), null); | |
} | |
} | |
/** | |
* INTERNAL: | |
* This method adds the lock value to the translation row of the | |
* passed in query. depending on the storage flag, the value is | |
* either retrieved from the cache of the object. | |
*/ | |
public void addLockValuesToTranslationRow(ObjectLevelModifyQuery query) { | |
Object value; | |
if (isStoredInCache()) { | |
value = query.getSession().getIdentityMapAccessorInstance().getWriteLockValue(query.getPrimaryKey(), query.getObject().getClass(), getDescriptor()); | |
} else { | |
value = lockValueFromObject(query.getObject()); | |
} | |
if (value == null) { | |
if (query.isDeleteObjectQuery()) { | |
throw OptimisticLockException.noVersionNumberWhenDeleting(query.getObject(), query); | |
} else { | |
throw OptimisticLockException.noVersionNumberWhenUpdating(query.getObject(), query); | |
} | |
} | |
// EL bug 319759 | |
if (query.isUpdateObjectQuery()) { | |
query.setShouldValidateUpdateCallCacheUse(true); | |
} | |
query.getTranslationRow().put(this.writeLockField, value); | |
} | |
/** | |
* INTERNAL: | |
* When given an expression, this method will return a new expression with | |
* the optimistic locking values included. The values are taken from the | |
* passed in database row. This expression will be used in a delete call. | |
*/ | |
public Expression buildDeleteExpression(DatabaseTable table, Expression mainExpression, AbstractRecord row) { | |
//use the same expression as update | |
return buildUpdateExpression(table, mainExpression, row, null); | |
} | |
/** | |
* INTERNAL: | |
* Returns an expression that will be used for both the update and | |
* delete where clause | |
*/ | |
protected Expression buildExpression() { | |
ExpressionBuilder builder = new ExpressionBuilder(); | |
return builder.getField(getWriteLockField()).equal(builder.getParameter(getWriteLockField())); | |
} | |
/** | |
* INTERNAL: | |
* When given an expression, this method will return a new expression | |
* with the optimistic locking values included. The values are taken | |
* from the passed in database row. This expression will be used in | |
* an update call. | |
*/ | |
public Expression buildUpdateExpression(DatabaseTable table, Expression mainExpression, AbstractRecord row, AbstractRecord row2) { | |
if (cachedExpression == null) { | |
cachedExpression = buildExpression(); | |
} | |
if (getWriteLockField().getTableName().equals(table.getName())) { | |
return mainExpression.and(cachedExpression); | |
} | |
return mainExpression; | |
} | |
/** | |
* INTERNAL: | |
* Clone the policy | |
*/ | |
public Object clone() { | |
try { | |
return super.clone(); | |
} catch (CloneNotSupportedException e) { | |
return null; | |
} | |
} | |
/** | |
* INTERNAL: | |
* Indicates that compareWriteLockValues method is supported by the policy. | |
*/ | |
public boolean supportsWriteLockValuesComparison() { | |
return true; | |
} | |
/** | |
* INTERNAL: | |
* This method compares two writeLockValues. | |
* The writeLockValues should be non-null and of type Number. | |
* Returns: | |
* -1 if value1 is less (older) than value2; | |
* 0 if value1 equals value2; | |
* 1 if value1 is greater (newer) than value2. | |
* Throws: | |
* NullPointerException if the passed value is null; | |
* ClassCastException if the passed value is of a wrong type. | |
*/ | |
public int compareWriteLockValues(Object value1, Object value2) { | |
long longValue1 = ((Number)value1).longValue(); | |
long longValue2 = ((Number)value2).longValue(); | |
if ( longValue1 < longValue2 ) return -1; | |
if ( longValue1 == longValue2 ) return 0; | |
return 1; | |
} | |
/** | |
* INTERNAL: | |
* Return the default version locking filed java type, default is BigDecimal | |
*/ | |
protected Class getDefaultLockingFieldType() { | |
return ClassConstants.LONG; | |
} | |
/** | |
* INTERNAL: | |
* This is the base value that is older than all other values, it is used in the place of | |
* null in some situations. | |
*/ | |
public Object getBaseValue() { | |
return Long.valueOf(0); | |
} | |
/** | |
* INTERNAL: | |
*/ | |
protected ClassDescriptor getDescriptor() { | |
return descriptor; | |
} | |
/** | |
* INTERNAL: | |
* returns the initial locking value | |
*/ | |
protected Object getInitialWriteValue(AbstractSession session) { | |
return Long.valueOf(1); | |
} | |
/** | |
* ADVANCED: | |
* returns the LockOnChange mode for this policy. This mode specifies if a | |
* Optimistic Write lock should be enforced on this entity when a set of mappings are changed. | |
* Unfortunately this locking policy can not enforce an optimistic write lock unless a FK or DTF field | |
* has changed so this type returns LockOnChange.NONE | |
*/ | |
public LockOnChange getLockOnChangeMode(){ | |
return this.lockOnChangeMode; | |
} | |
/** | |
* INTERNAL: | |
* This method gets the write lock value from either the cache or | |
* the object stored in the query. It then returns the new incremented value. | |
*/ | |
public Object getNewLockValue(ModifyQuery query) { | |
Class objectClass = query.getDescriptor().getJavaClass(); | |
Number value; | |
Number newWriteLockValue = null; | |
if (isStoredInCache()) { | |
value = (Number)query.getSession().getIdentityMapAccessorInstance().getWriteLockValue(((WriteObjectQuery)query).getPrimaryKey(), objectClass, getDescriptor()); | |
} else { | |
value = (Number)lockValueFromObject(((ObjectLevelModifyQuery)query).getObject()); | |
} | |
if (value == null) { | |
throw OptimisticLockException.noVersionNumberWhenUpdating(((ObjectLevelModifyQuery)query).getObject(), (ObjectLevelModifyQuery)query); | |
} | |
// Increment the value, this goes to the database | |
newWriteLockValue = incrementWriteLockValue(value); | |
return newWriteLockValue; | |
} | |
/** | |
* INTERNAL: | |
* This method returns any of the fields that are not mapped in | |
* the object. In the case of the value being stored in the | |
* cache, a vector with one value is returned. In the case | |
* of being stored in the object, an empty vector is returned. | |
*/ | |
protected Vector getUnmappedFields() { | |
Vector fields = new Vector(1); | |
if (isStoredInCache()) { | |
fields.addElement(getWriteLockField()); | |
} | |
return fields; | |
} | |
/** | |
* INTERNAL: | |
* Return the value that should be stored in the identity map. | |
* If the value is stored in the object, then return a null. | |
*/ | |
public Object getValueToPutInCache(AbstractRecord row, AbstractSession session) { | |
if (isStoredInCache()) { | |
return row.get(getWriteLockField()); | |
} else { | |
return null; | |
} | |
} | |
/** | |
* PUBLIC: | |
* Return the number of versions different between these objects. | |
* @param currentValue the new lock value | |
* @param domainObject the object containing the version to be compared to | |
* @param primaryKeys a vector containing the primary keys of the domainObject | |
* @param session the session to be used with the comparison | |
*/ | |
public int getVersionDifference(Object currentValue, Object domainObject, Object primaryKeys, AbstractSession session) { | |
Number writeLockFieldValue; | |
Number newWriteLockFieldValue = (Number)currentValue; | |
// If null, was an insert, use 0. | |
if (newWriteLockFieldValue == null) { | |
newWriteLockFieldValue = Long.valueOf(0); | |
} | |
if (isStoredInCache()) { | |
writeLockFieldValue = (Number)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKeys, domainObject.getClass(), getDescriptor()); | |
} else { | |
writeLockFieldValue = (Number)lockValueFromObject(domainObject); | |
} | |
if (writeLockFieldValue == null){ | |
writeLockFieldValue = Long.valueOf(0); | |
} | |
return (int)(newWriteLockFieldValue.longValue() - writeLockFieldValue.longValue()); | |
} | |
/** | |
* INTERNAL: | |
* Return the write lock field. | |
*/ | |
public DatabaseField getWriteLockField() { | |
return writeLockField; | |
} | |
/** | |
* PUBLIC: | |
* Return the field name of the field that stores the write lock value. | |
*/ | |
public String getWriteLockFieldName() { | |
return getWriteLockField().getQualifiedName(); | |
} | |
/** | |
* INTERNAL: | |
* Retrun an expression that updates the write lock | |
*/ | |
public Expression getWriteLockUpdateExpression(ExpressionBuilder builder, AbstractSession session) { | |
return ExpressionMath.add(builder.getField(writeLockField.getName()), 1); | |
} | |
/** | |
* INTERNAL: | |
* This method will return the optimistic lock value for the object | |
*/ | |
public Object getWriteLockValue(Object domainObject, Object primaryKey, AbstractSession session) { | |
Number writeLockFieldValue; | |
if (isStoredInCache()) { | |
if (primaryKey == null) { | |
return null; | |
} | |
writeLockFieldValue = (Number)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); | |
} else { | |
writeLockFieldValue = (Number)lockValueFromObject(domainObject); | |
} | |
return writeLockFieldValue; | |
} | |
/** | |
* INTERNAL: | |
* Adds 1 to the value passed in. | |
*/ | |
protected Number incrementWriteLockValue(Number numberValue) { | |
return Long.valueOf(numberValue.longValue() + 1); | |
} | |
/** | |
* INTERNAL: | |
* It is responsible for initializing the policy; | |
*/ | |
public void initialize(AbstractSession session) { | |
DatabaseMapping mapping = this.descriptor.getObjectBuilder().getMappingForField(getWriteLockField()); | |
if (mapping == null) { | |
if (isStoredInObject()) { | |
if (this.descriptor.getObjectBuilder().getReadOnlyMappingsForField(getWriteLockField()) != null) { | |
mapping = this.descriptor.getObjectBuilder().getReadOnlyMappingsForField(getWriteLockField()).get(0); | |
session.getIntegrityChecker().handleError(DescriptorException.mappingCanNotBeReadOnly(mapping)); | |
} else { | |
session.getIntegrityChecker().handleError(OptimisticLockException.mustHaveMappingWhenStoredInObject(this.descriptor.getJavaClass())); | |
} | |
} else { | |
return; | |
} | |
} | |
if (isStoredInCache()) { | |
session.getIntegrityChecker().handleError(DescriptorException.mustBeReadOnlyMappingWhenStoredInCache(mapping)); | |
} | |
// PERF: Cache the mapping if direct. | |
if (mapping.isDirectToFieldMapping() && (this.descriptor.getObjectBuilder().getReadOnlyMappingsForField(getWriteLockField()) == null)) { | |
this.lockMapping = (AbstractDirectMapping)mapping; | |
} | |
// If the version field is not in the primary table, then they cannot be batched together. | |
if ((this.descriptor.getTables().size() > 0) && !getWriteLockField().getTable().equals(this.descriptor.getTables().get(0))) { | |
this.descriptor.setHasMultipleTableConstraintDependecy(true); | |
} | |
} | |
/** | |
* INTERNAL: | |
* It is responsible for initializing the policy properties; | |
*/ | |
public void initializeProperties() { | |
DatabaseField dbField = getWriteLockField(); | |
dbField = descriptor.buildField(dbField); | |
setWriteLockField(dbField); | |
if (isStoredInCache() && (dbField.getType() == null)) { | |
// Set the default type, only if un-mapped. | |
dbField.setType(getDefaultLockingFieldType()); | |
} | |
Enumeration enumtr = this.getUnmappedFields().elements(); | |
while (enumtr.hasMoreElements()) { | |
DatabaseField lockField; | |
lockField = (DatabaseField)enumtr.nextElement(); | |
descriptor.getFields().addElement(lockField); | |
} | |
} | |
/** | |
* PUBLIC: | |
* Return true if the policy uses cascade locking. | |
*/ | |
public boolean isCascaded() { | |
return isCascaded; | |
} | |
/** | |
* INTERNAL: | |
* Compares the value with the value from the object (or cache). | |
* Will return true if the currentValue is newer than the domainObject. | |
*/ | |
public boolean isNewerVersion(Object currentValue, Object domainObject, Object primaryKey, AbstractSession session) { | |
Number writeLockFieldValue; | |
Number newWriteLockFieldValue = (Number)currentValue; | |
if (isStoredInCache()) { | |
writeLockFieldValue = (Number)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); | |
} else { | |
writeLockFieldValue = (Number)lockValueFromObject(domainObject); | |
} | |
return isNewerVersion(newWriteLockFieldValue, writeLockFieldValue); | |
} | |
/** | |
* INTERNAL: | |
* Compares the value from the row and from the object (or cache). | |
* Will return true if the row is newer than the object. | |
*/ | |
public boolean isNewerVersion(AbstractRecord databaseRow, Object domainObject, Object primaryKey, AbstractSession session) { | |
Number writeLockFieldValue; | |
Number newWriteLockFieldValue = (Number)databaseRow.get(getWriteLockField()); | |
if (isStoredInCache()) { | |
writeLockFieldValue = (Number)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); | |
} else { | |
writeLockFieldValue = (Number)lockValueFromObject(domainObject); | |
} | |
return isNewerVersion(newWriteLockFieldValue, writeLockFieldValue); | |
} | |
/** | |
* INTERNAL: | |
* Compares two values. | |
* Will return true if the firstLockFieldValue is newer than the secondWriteLockFieldValue. | |
*/ | |
public boolean isNewerVersion(Object firstLockFieldValue, Object secondWriteLockFieldValue) { | |
Number firstValue = (Number)firstLockFieldValue;//domain object/clone | |
Number secondValue = (Number)secondWriteLockFieldValue;//base value/cache | |
// 2.5.1.6 if the write lock value is null, then what ever we have is treated as newer. | |
if (firstValue == null) { | |
return false; | |
} | |
// bug 6342382: first is not null, second is null, so we know first>second. | |
if(secondValue == null) { | |
return true; | |
} | |
if (firstValue.longValue() > secondValue.longValue()){ | |
return true; | |
} | |
return false; | |
} | |
/** | |
* PUBLIC: | |
* Return true if the lock value is stored in the cache. | |
*/ | |
public boolean isStoredInCache() { | |
return lockValueStored == IN_CACHE; | |
} | |
/** | |
* PUBLIC: | |
* Return true if the lock value is stored in the object. | |
*/ | |
public boolean isStoredInObject() { | |
return lockValueStored == IN_OBJECT; | |
} | |
/** | |
* INTERNAL: | |
* Retrieves the lock value from the object. | |
*/ | |
protected Object lockValueFromObject(Object domainObject) { | |
// PERF: If mapping with a direct mapping get from cached mapping. | |
if (this.lockMapping != null) { | |
return this.lockMapping.getAttributeValueFromObject(domainObject); | |
} else { | |
return this.descriptor.getObjectBuilder().getBaseValueForField(this.writeLockField, domainObject); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Returns the mapping that will be used to access the version value from an object. | |
*/ | |
public AbstractDirectMapping getVersionMapping(){ | |
if (this.lockMapping != null){ | |
return this.lockMapping; | |
}else{ | |
return (AbstractDirectMapping)this.descriptor.getObjectBuilder().getBaseMappingForField(this.writeLockField); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Only applicable when the value is stored in the cache. Will merge with the parent unit of work. | |
*/ | |
public void mergeIntoParentCache(UnitOfWorkImpl uow, Object primaryKey, Object object) { | |
if (isStoredInCache()) { | |
Object parentValue = uow.getParentIdentityMapSession(descriptor, false, false).getIdentityMapAccessorInstance().getWriteLockValue(primaryKey, object.getClass(), getDescriptor()); | |
uow.getIdentityMapAccessor().updateWriteLockValue(primaryKey, object.getClass(), parentValue); | |
} | |
} | |
/** | |
* INTERNAL: | |
* This method should merge changes from the parent into the child. | |
* | |
* #see this method in VersionLockingPolicy | |
*/ | |
public void mergeIntoParentCache(CacheKey unitOfWorkCacheKey, CacheKey parentSessionCacheKey){ | |
if (isStoredInCache() && unitOfWorkCacheKey != null && parentSessionCacheKey != null) { | |
unitOfWorkCacheKey.setWriteLockValue(parentSessionCacheKey.getWriteLockValue()); | |
} | |
} | |
/** | |
* INTERNAL: | |
*/ | |
public void setDescriptor(ClassDescriptor descriptor) { | |
this.descriptor = descriptor; | |
} | |
/** | |
* PUBLIC: | |
* Set whether to store the lock in the cache or in the object. | |
* @param isStoredInCache set this to true if you would like to store lock in the cache and set it | |
* to false if you would like to store it in the object. | |
*/ | |
public void setIsStoredInCache(boolean isStoredInCache) { | |
if (isStoredInCache) { | |
storeInCache(); | |
} else { | |
storeInObject(); | |
} | |
} | |
/** | |
* PUBLIC: | |
* Set whether to use cascade locking on the policy. | |
* @param isCascaded set this to true if you would like cascade the locking | |
* and set it to false if you would like no cascade locking. | |
*/ | |
public void setIsCascaded(boolean isCascaded) { | |
this.isCascaded = isCascaded; | |
} | |
/** | |
* INTERNAL: | |
* This method must be included in any locking policy. | |
* Put the initial writelock value into the modifyRow. | |
*/ | |
public void setupWriteFieldsForInsert(ObjectLevelModifyQuery query) { | |
Object lockValue = getInitialWriteValue(query.getSession()); | |
ObjectChangeSet objectChangeSet = query.getObjectChangeSet(); | |
if (objectChangeSet != null) { | |
objectChangeSet.setInitialWriteLockValue(lockValue); | |
} | |
updateWriteLockValueForWrite(query, lockValue); | |
} | |
/** | |
* INTERNAL: | |
* Update the row, object and change set with the version value. | |
* This handles the version being mapped in nested aggregates, writable or read-only. | |
*/ | |
protected void updateWriteLockValueForWrite(ObjectLevelModifyQuery query, Object lockValue) { | |
// PERF: direct-access. | |
query.getModifyRow().put(this.writeLockField, lockValue); | |
updateObjectWithWriteValue(query, lockValue); | |
} | |
/** | |
* INTERNAL: | |
* Returns true if the policy has been set to set an optimistic read lock when a owning mapping changes. | |
*/ | |
public boolean shouldUpdateVersionOnOwnedMappingChange(){ | |
return this.lockOnChangeMode == LockOnChange.OWNING; | |
} | |
/** | |
* INTERNAL: | |
* Returns true if the policy has been set to set an optimistic read lock when any mapping changes. | |
*/ | |
public boolean shouldUpdateVersionOnMappingChange(){ | |
return this.lockOnChangeMode == LockOnChange.ALL; | |
} | |
public void updateObjectWithWriteValue(ObjectLevelModifyQuery query, Object lockValue){ | |
AbstractSession session = query.getSession(); | |
Object object = query.getObject(); | |
ObjectChangeSet objectChangeSet = query.getObjectChangeSet(); | |
if (objectChangeSet == null) { | |
if (session.isUnitOfWork() && (((UnitOfWorkImpl)session).getUnitOfWorkChangeSet() != null)) { | |
// For aggregate collections the change set may be null, as they use the old commit still. | |
objectChangeSet = (ObjectChangeSet)((UnitOfWorkImpl)session).getUnitOfWorkChangeSet().getObjectChangeSetForClone(object); | |
} | |
} | |
// PERF: handle normal case faster. | |
if (this.lockMapping != null) { | |
// converted to the correct (for the mapping) type lock value. | |
Object convertedLockValue = this.lockMapping.getObjectValue(lockValue, session); | |
if (objectChangeSet != null && (!objectChangeSet.isNew() || query.getDescriptor().shouldUseFullChangeSetsForNewObjects())) { | |
Object oldValue = this.lockMapping.getAttributeValueFromObject(object); | |
this.lockMapping.setAttributeValueInObject(object, convertedLockValue); | |
objectChangeSet.setWriteLockValue(lockValue); | |
// Don't use ObjectChangeSet.updateChangeRecordForAttributeWithMappedObject to avoid unnecessary conversion - convertedLockValue is already converted. | |
DirectToFieldChangeRecord changeRecord = new DirectToFieldChangeRecord(objectChangeSet); | |
changeRecord.setAttribute(this.lockMapping.getAttributeName()); | |
changeRecord.setMapping(this.lockMapping); | |
changeRecord.setNewValue(convertedLockValue); | |
changeRecord.setOldValue(oldValue); | |
objectChangeSet.addChange(changeRecord); | |
} else { | |
this.lockMapping.setAttributeValueInObject(object, convertedLockValue); | |
} | |
} else { | |
// CR#3173211 | |
// If the value is stored in the cache or object, there still may | |
// be read-only mappings for it, so the object must always be updated for | |
// any writable or read-only mappings for the version value. | |
// Reuse the method used for returning as has the same requirements. | |
ObjectBuilder objectBuilder = this.descriptor.getObjectBuilder(); | |
AbstractRecord record = objectBuilder.createRecord(1, session); | |
record.put(this.writeLockField, lockValue); | |
if (objectChangeSet != null) { | |
objectChangeSet.setWriteLockValue(lockValue); | |
} | |
objectBuilder.assignReturnRow(object, session, record, objectChangeSet); | |
} | |
} | |
/** | |
* ADVANCED: | |
* Sets the LockOnChange mode for this policy. This mode specifies if a | |
* Optimistic Write lock should be enforced on this entity when set of mappings are changed. | |
*/ | |
public void setLockOnChangeMode(LockOnChange lockOnChangeMode){ | |
this.lockOnChangeMode = lockOnChangeMode; | |
} | |
/** | |
* ADVANCED: | |
* Set the write lock field. | |
* This can be used for advanced field types, such as XML nodes, or to set the field type. | |
*/ | |
public void setWriteLockField(DatabaseField writeLockField) { | |
this.writeLockField = writeLockField; | |
} | |
/** | |
* PUBLIC: | |
* Set the write lock field name. | |
* @param writeLockFieldName the name of the field to lock against. | |
*/ | |
public void setWriteLockFieldName(String writeLockFieldName) { | |
setWriteLockField(new DatabaseField(writeLockFieldName)); | |
} | |
/** | |
* PUBLIC: | |
* Configure the version lock value to be stored in the cache. | |
* This allows for the object not to require to store its version value as an attribute. | |
* Note: if using a stateless model where the object can be passed to a client and then | |
* later updated in a different transaction context, then the version lock value should | |
* not be stored in the cache, but in the object to ensure it is the correct value for | |
* that object. This is the default. | |
*/ | |
public void storeInCache() { | |
lockValueStored = IN_CACHE; | |
} | |
/** | |
* PUBLIC: | |
* Configure the version lock value to be stored in the object. | |
* The object must define a mapping and an attribute to store the version value. | |
* Note: the value will be updated internally by EclipseLink and should not be updated | |
* by the application. | |
*/ | |
public void storeInObject() { | |
lockValueStored = IN_OBJECT; | |
} | |
/** | |
* INTERNAL: | |
* This method updates the modify row, and the domain object | |
* with the new lock value. | |
*/ | |
public void updateRowAndObjectForUpdate(ObjectLevelModifyQuery query, Object domainObject) { | |
Object lockValue = getNewLockValue(query); | |
if (isStoredInCache()) { | |
query.getSession().getIdentityMapAccessor().updateWriteLockValue(query.getPrimaryKey(), domainObject.getClass(), lockValue); | |
} | |
updateWriteLockValueForWrite(query, lockValue); | |
} | |
/** | |
* INTERNAL: | |
* This method updates the modify row with the old lock value. | |
*/ | |
public void writeLockValueIntoRow(ObjectLevelModifyQuery query, Object domainObject) { | |
Object lockValue = getWriteLockValue(domainObject, query.getPrimaryKey(), query.getSession()); | |
query.getModifyRow().put(this.writeLockField, lockValue); | |
if (isStoredInCache()) { | |
query.getSession().getIdentityMapAccessor().updateWriteLockValue(query.getPrimaryKey(), domainObject.getClass(), lockValue); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Check the row count for lock failure. | |
*/ | |
public void validateDelete(int rowCount, Object object, DeleteObjectQuery query) { | |
if (rowCount <= 0) { | |
// Mark the object as invalid in the session cache, only if version is the same as in query. | |
Object primaryKey = query.getPrimaryKey(); | |
AbstractSession session = query.getSession().getParentIdentityMapSession(query, true, true); | |
CacheKey cacheKey = session.getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, query.getReferenceClass(), query.getDescriptor(), false); | |
if ((cacheKey != null) && (cacheKey.getObject() != null) && (query.getObjectChangeSet() != null)) { | |
Object queryVersion = query.getObjectChangeSet().getInitialWriteLockValue(); | |
Object cacheVersion = getWriteLockValue(cacheKey.getObject(), primaryKey, session); | |
if (compareWriteLockValues(queryVersion, cacheVersion) != 0) { | |
cacheKey.setInvalidationState(CacheKey.CACHE_KEY_INVALID); | |
} | |
} | |
throw OptimisticLockException.objectChangedSinceLastReadWhenDeleting(object, query); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Check the row count for lock failure. | |
*/ | |
public void validateUpdate(int rowCount, Object object, WriteObjectQuery query) { | |
if (rowCount <= 0) { | |
// Mark the object as invalid in the session cache, only if version is the same as in query. | |
Object primaryKey = query.getPrimaryKey(); | |
AbstractSession session = query.getSession().getParentIdentityMapSession(query, true, true); | |
CacheKey cacheKey = session.getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, query.getReferenceClass(), query.getDescriptor(), false); | |
if ((cacheKey != null) && (cacheKey.getObject() != null) && (query.getObjectChangeSet() != null)) { | |
Object queryVersion = query.getObjectChangeSet().getInitialWriteLockValue(); | |
Object cacheVersion = getWriteLockValue(cacheKey.getObject(), primaryKey, session); | |
if (compareWriteLockValues(queryVersion, cacheVersion) >= 0) { | |
cacheKey.setInvalidationState(CacheKey.CACHE_KEY_INVALID); | |
} | |
} | |
throw OptimisticLockException.objectChangedSinceLastReadWhenUpdating(object, query); | |
} | |
} | |
} |