/******************************************************************************* | |
* 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.util.*; | |
import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy; | |
import org.eclipse.persistence.internal.sessions.AbstractRecord; | |
import org.eclipse.persistence.internal.sessions.AbstractSession; | |
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; | |
import org.eclipse.persistence.internal.helper.*; | |
import org.eclipse.persistence.internal.identitymaps.CacheKey; | |
import org.eclipse.persistence.queries.*; | |
import org.eclipse.persistence.expressions.*; | |
import org.eclipse.persistence.exceptions.*; | |
/** | |
* <p><b>Purpose</b>: An abstract superclass of some implementations of the OptimisticLockingPolicy | |
* interface. All of the subclasses of this class implement OptimisticLocking | |
* based on mapped fields in the object. These fields are only compared and not modified. | |
* Any modification (incrementing etc..) must be handled by the application. | |
* | |
* @see AllFieldsLockingPolicy | |
* @see ChangedFieldsLockingPolicy | |
* @see SelectedFieldsLockingPolicy | |
* @since TopLink 2.1 | |
* @author Peter Krogh | |
*/ | |
public abstract class FieldsLockingPolicy implements OptimisticLockingPolicy { | |
protected ClassDescriptor descriptor; | |
protected List<DatabaseField> allNonPrimaryKeyFields; | |
/** | |
* PUBLIC: | |
* Create a new field locking policy. | |
* A field locking policy is based on locking on a subset of fields by comparing with their previous values to detect field-level collisions. | |
* Note: the unit of work must be used for all updates when using field locking. | |
*/ | |
public FieldsLockingPolicy() { | |
super(); | |
} | |
/** | |
* INTERNAL: | |
* Add update fields for template row. | |
* These are any unmapped fields required to write in an update. | |
* Since all fields are mapped, there is nothing required. | |
*/ | |
public void addLockFieldsToUpdateRow(AbstractRecord Record, AbstractSession session) { | |
// Nothing required. | |
} | |
/** | |
* INTERNAL: | |
* Values to be included in the locking mechanism are added | |
* to the translation row. Set the translation row to all the original field values. | |
*/ | |
public abstract void addLockValuesToTranslationRow(ObjectLevelModifyQuery query); | |
/** | |
* INTERNAL: | |
* Returns the fields that should be compared in the where clause. | |
* In this case, it is all the fields, except for the primary key | |
* and class indicator fields. | |
* This is called during lazy initialization. | |
*/ | |
protected List buildAllNonPrimaryKeyFields() { | |
List fields = new ArrayList(); | |
for (DatabaseField dbField : descriptor.getSelectionFields()) { | |
if (!isPrimaryKey(dbField)) { | |
if (descriptor.hasInheritance()) { | |
DatabaseField classField = descriptor.getInheritancePolicy().getClassIndicatorField(); | |
if (!((classField == null) || dbField.equals(classField))) { | |
fields.add(dbField); | |
} | |
} else { | |
fields.add(dbField); | |
} | |
} | |
} | |
/* CR#... nullpoint occurs if null is returned, not sure why this was here. | |
if (fields.isEmpty()) { | |
return null; | |
}*/ | |
return fields; | |
} | |
/** | |
* 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) { | |
return mainExpression.and(buildExpression(table, row, null, mainExpression.getBuilder())); | |
} | |
/** | |
* INTERNAL: | |
* returns the expression to be used in both the delete and update where clause. | |
*/ | |
protected Expression buildExpression(DatabaseTable table, AbstractRecord transRow, AbstractRecord modifyRow, ExpressionBuilder builder) { | |
Expression exp = null; | |
DatabaseField field; | |
Iterator<DatabaseField> iterator = getFieldsToCompare(table, transRow, modifyRow).iterator(); | |
if (iterator.hasNext()) { | |
field = iterator.next();//First element | |
exp = builder.getField(field).equal(builder.getParameter(field)); | |
} | |
while (iterator.hasNext()) { | |
field = iterator.next(); | |
exp = exp.and(builder.getField(field).equal(builder.getParameter(field))); | |
} | |
return exp; | |
} | |
/** | |
* INTERNAL: | |
* This method must be included in any locking policy. 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 buildUpdateExpression(DatabaseTable table, Expression mainExpression, AbstractRecord transRow, AbstractRecord modifyRow) { | |
return mainExpression.and(buildExpression(table, transRow, modifyRow, mainExpression.getBuilder())); | |
} | |
/** | |
* INTERNAL: | |
* Clone the policy | |
*/ | |
public Object clone() { | |
try { | |
return super.clone(); | |
} catch (CloneNotSupportedException e) { | |
return null; | |
} | |
} | |
/** | |
* INTERNAL: | |
* Indicates whether compareWriteLockValues method is supported by the policy. | |
* Numeric or timestamp lock values could be compared: | |
* for every pair of values v1 and v2 - either v1<v2; or v1==v2; or v1>v2. | |
* However it's impossible to compare values for FieldsLockingPolicy for two reasons: | |
* 1. there is no "linear order": v1<v2 and v>v2 is not defined: either v1==v2 or v1!=v2; | |
* 2. locking value is not a single field which is not part of mapped object value | |
* but rather a set of object's mapped fields. That means any object's mapped attribute change | |
* is potentially a change of the locking value. | |
* For ChangedFieldsLockingPolicy every mapped attribute's change is a change of locking value. | |
* The pattern used by versioning: "if the original locking value is unchanged | |
* then the object hasn't been changed outside of the application", which allows | |
* to distinguish between the change made inside and outside the application, | |
* doesn't work for fields locking. | |
* It degenerates into useless pattern: "if the original locking value is unchanged | |
* then the object hasn't been changed". | |
* | |
* Use compareWriteLockValues method only if this method returns true. | |
*/ | |
public boolean supportsWriteLockValuesComparison() { | |
return false; | |
} | |
/** | |
* INTERNAL: | |
* This method shouldn't be called if supportsWriteLockValuesComparison() returns false. | |
* This method compares two writeLockValues. | |
* The writeLockValues should be non-null and of the correct type. | |
* 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){ | |
// should never be called because supportsWriteLockValuesComparison() returns false. | |
return -1; | |
} | |
/** | |
* INTERNAL: | |
* Returns the fields that should be compared in the where clause. | |
* In this case, it is all the fields, except for the primary key | |
* and class indicator field. | |
*/ | |
protected List<DatabaseField> getAllNonPrimaryKeyFields() { | |
if (allNonPrimaryKeyFields == null) { | |
allNonPrimaryKeyFields = buildAllNonPrimaryKeyFields(); | |
} | |
return allNonPrimaryKeyFields; | |
} | |
/** | |
* INTERNAL: | |
* filter the fields based on the passed in table. Only return fields of this table. | |
*/ | |
protected List<DatabaseField> getAllNonPrimaryKeyFields(DatabaseTable table) { | |
List<DatabaseField> filteredFields = new ArrayList<DatabaseField>(); | |
for (DatabaseField dbField : getAllNonPrimaryKeyFields()) { | |
if (dbField.getTableName().equals(table.getName())) { | |
filteredFields.add(dbField); | |
} | |
} | |
return filteredFields; | |
} | |
/** | |
* 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 null; // this locking type does not store values in the cache | |
} | |
/** | |
* INTERNAL: | |
* Returns the fields that should be compared in the where clause. | |
* This method must be implemented by the subclass | |
*/ | |
protected abstract List<DatabaseField> getFieldsToCompare(DatabaseTable table, AbstractRecord transRow, AbstractRecord modifyRow); | |
/** | |
* INTERNAL: | |
* Return the write lock field. | |
*/ | |
public DatabaseField getWriteLockField() { | |
// Does not apply to any field locking policy, so return null | |
return null; | |
} | |
/** | |
* INTERNAL: | |
*/ | |
public Expression getWriteLockUpdateExpression(ExpressionBuilder builder, AbstractSession session) { | |
// Does not apply to any field locking policy, so return null | |
return null; | |
} | |
/** | |
* 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 LockOnChange.NONE; | |
} | |
/** | |
* 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) { | |
return null; | |
} | |
/** | |
* INTERNAL: | |
* Return the number of version difference between the two states of the object. | |
*/ | |
public int getVersionDifference(Object currentValue, Object domainObject, Object primaryKeys, AbstractSession session) { | |
// There is no way of knowing what the difference is so return 0 | |
// This should never be called for field locking. | |
return 0; | |
} | |
/** | |
* INTERNAL: | |
* This method will return the optimistic lock value for the object | |
*/ | |
public Object getWriteLockValue(Object domainObject, Object primaryKey, AbstractSession session) { | |
//There is no way of knowing if this value is newer or not, so always return true. | |
return null; | |
} | |
/** | |
* INTERNAL: | |
* It is responsible for initializing the policy; | |
*/ | |
public void initialize(AbstractSession session) { | |
// If the version field is not in the primary table, then they cannot be batched together. | |
if (this.descriptor.getTables().size() > 0) { | |
this.descriptor.setHasMultipleTableConstraintDependecy(true); | |
} | |
} | |
/** | |
* INTERNAL: | |
* It is responsible for initializing the policy; | |
*/ | |
public void initializeProperties() { | |
//nothing to do | |
} | |
/** | |
* PUBLIC: | |
* Return true if the lock value is stored in the cache. | |
*/ | |
public boolean isStoredInCache() { | |
return false; | |
} | |
/** | |
* PUBLIC: | |
* Return true if the policy uses cascade locking. Currently, not supported | |
* on this policy at this time. | |
*/ | |
public boolean isCascaded() { | |
return false; | |
} | |
/** | |
* INTERNAL: | |
* Compares the value and the value from the object | |
* (or cache). Will return true if the object is newer | |
* than the row. | |
*/ | |
public boolean isNewerVersion(Object currentValue, Object domainObject, Object primaryKey, AbstractSession session) { | |
//There is no way of knowing if this value is newer or not, so always return true. | |
return true; | |
} | |
/** | |
* INTERNAL: | |
* Compares the value from the row and from the object | |
* (or cache). Will return true if the object is newer | |
* than the row. | |
*/ | |
public boolean isNewerVersion(AbstractRecord Record, Object domainObject, Object primaryKey, AbstractSession session) { | |
//There is no way of knowing if this value is newer or not, so always return true. | |
return true; | |
} | |
/** | |
* INTERNAL: | |
* Returns whether or not this field is a primary key. | |
* This method will also return true for secondary table primarykeys | |
*/ | |
protected boolean isPrimaryKey(DatabaseField dbField) { | |
if (descriptor.getPrimaryKeyFields().contains(dbField)) { | |
return true; | |
} else { | |
if (descriptor.isMultipleTableDescriptor()) { | |
for (Iterator enumtr = descriptor.getAdditionalTablePrimaryKeyFields().values().iterator(); | |
enumtr.hasNext();) { | |
if (((Map)enumtr.next()).containsKey(dbField)) { | |
return true; | |
} | |
} | |
} | |
} | |
return false; | |
} | |
/** | |
* INTERNAL: | |
* Only applicable when the value is stored in the cache. | |
*/ | |
public void mergeIntoParentCache(UnitOfWorkImpl uow, Object primaryKey, Object object) { | |
// nothing to do | |
} | |
/** | |
* INTERNAL: | |
* This method should merge changes from the parent into the child. | |
* | |
* #see this method in VersionLockingPolicy | |
*/ | |
public void mergeIntoParentCache(CacheKey unitOfWorkCacheKey, CacheKey parentSessionCacheKey){ | |
// nothing to do | |
} | |
/** | |
* INTERNAL: Set method for all the primary keys | |
*/ | |
protected void setAllNonPrimaryKeyFields(List allNonPrimaryKeyFields) { | |
this.allNonPrimaryKeyFields = allNonPrimaryKeyFields; | |
} | |
/** | |
* INTERNAL: Set method for the descriptor | |
*/ | |
public void setDescriptor(ClassDescriptor descriptor) { | |
this.descriptor = descriptor; | |
} | |
/** | |
* 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. | |
* Unfortunately this locking policy can not always force an optimistic lock unless the core fields have changed | |
*/ | |
public void setLockOnChangeMode(LockOnChange lockOnChangeMode){ | |
//no-op for this type | |
} | |
/** | |
* INTERNAL: | |
* Put the initial writelock value into the modifyRow. | |
* There is nothing to do because all the lock values are in the mappings. | |
*/ | |
public void setupWriteFieldsForInsert(ObjectLevelModifyQuery query) { | |
//nothing to do. | |
} | |
/** | |
* INTERNAL: | |
* Nothing to do because all updates are handled by the application | |
*/ | |
public void updateRowAndObjectForUpdate(ObjectLevelModifyQuery query, Object domainObject) { | |
//nothing to do | |
} | |
/** | |
* INTERNAL: | |
* Returns true if the policy has been set to set an optimistic read lock when a owning mapping changes. | |
* Unfortunately this locking policy can not always force an optimistic lock unless the core fields have changed | |
*/ | |
public boolean shouldUpdateVersionOnOwnedMappingChange(){ | |
return false; | |
} | |
/** | |
* INTERNAL: | |
* Returns true if the policy has been set to set an optimistic read lock when any mapping changes. | |
* Unfortunately this locking policy can not always force an optimistic lock unless the core fields have changed | |
*/ | |
public boolean shouldUpdateVersionOnMappingChange(){ | |
return false; | |
} | |
/** | |
* 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. | |
query.getSession().getParentIdentityMapSession(query, true, true).getIdentityMapAccessor().invalidateObject(object); | |
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. | |
query.getSession().getParentIdentityMapSession(query, true, true).getIdentityMapAccessor().invalidateObject(object); | |
throw OptimisticLockException.objectChangedSinceLastReadWhenUpdating(object, query); | |
} | |
} | |
/** | |
* INTERNAL: | |
* throw an exception if not inside a unit of work at this point | |
*/ | |
protected void verifyUsage(AbstractSession session) { | |
if (!session.isUnitOfWork()) { | |
throw ValidationException.fieldLevelLockingNotSupportedWithoutUnitOfWork(); | |
} | |
} | |
} |