| /* |
| * 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.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. |
| */ |
| protected 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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<DatabaseField> buildAllNonPrimaryKeyFields() { |
| List<DatabaseField> 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| public Expression buildUpdateExpression(DatabaseTable table, Expression mainExpression, AbstractRecord transRow, AbstractRecord modifyRow) { |
| return mainExpression.and(buildExpression(table, transRow, modifyRow, mainExpression.getBuilder())); |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone the policy |
| */ |
| @Override |
| 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 {@literal 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": {@literal 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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<>(); |
| 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. |
| */ |
| @Override |
| public <T> T 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. |
| */ |
| @Override |
| public DatabaseField getWriteLockField() { |
| // Does not apply to any field locking policy, so return null |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| public <T> T getValueToPutInCache(AbstractRecord row, AbstractSession session) { |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the number of version difference between the two states of the object. |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| public <T> T 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; |
| */ |
| @Override |
| 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; |
| */ |
| @Override |
| public void initializeProperties() { |
| //nothing to do |
| } |
| |
| /** |
| * PUBLIC: |
| * Return true if the lock value is stored in the cache. |
| */ |
| @Override |
| public boolean isStoredInCache() { |
| return false; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return true if the policy uses cascade locking. Currently, not supported |
| * on this policy at this time. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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<Map<DatabaseField, DatabaseField>> enumtr = descriptor.getAdditionalTablePrimaryKeyFields().values().iterator(); |
| enumtr.hasNext();) { |
| if (enumtr.next().containsKey(dbField)) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Only applicable when the value is stored in the cache. |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| public void mergeIntoParentCache(CacheKey unitOfWorkCacheKey, CacheKey parentSessionCacheKey){ |
| // nothing to do |
| } |
| |
| /** |
| * INTERNAL: Set method for all the primary keys |
| */ |
| protected void setAllNonPrimaryKeyFields(List<DatabaseField> allNonPrimaryKeyFields) { |
| this.allNonPrimaryKeyFields = allNonPrimaryKeyFields; |
| } |
| |
| /** |
| * INTERNAL: Set method for the descriptor |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| public void setupWriteFieldsForInsert(ObjectLevelModifyQuery query) { |
| //nothing to do. |
| } |
| |
| /** |
| * INTERNAL: |
| * Nothing to do because all updates are handled by the application |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| public boolean shouldUpdateVersionOnMappingChange(){ |
| return false; |
| } |
| /** |
| * 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. |
| query.getSession().getParentIdentityMapSession(query, true, true).getIdentityMapAccessor().invalidateObject(object); |
| 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. |
| 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(); |
| } |
| } |
| } |