| /* |
| * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| package org.eclipse.persistence.descriptors; |
| |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| public Object clone() { |
| try { |
| return super.clone(); |
| } catch (CloneNotSupportedException e) { |
| return null; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates that compareWriteLockValues method is supported by the policy. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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; |
| */ |
| @Override |
| 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; |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| public void mergeIntoParentCache(CacheKey unitOfWorkCacheKey, CacheKey parentSessionCacheKey){ |
| if (isStoredInCache() && unitOfWorkCacheKey != null && parentSessionCacheKey != null) { |
| unitOfWorkCacheKey.setWriteLockValue(parentSessionCacheKey.getWriteLockValue()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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); |
| } |
| } |
| } |