All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.descriptors.FieldsLockingPolicy Maven / Gradle / Ivy

There is a newer version: 4.0.2
Show newest version
/*
 * Copyright (c) 1998, 2018 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.*;

/**
 * 

Purpose: 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 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 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 {@literal v1v2}. * However it's impossible to compare values for FieldsLockingPolicy for two reasons: * 1. there is no "linear order": {@literal v1v2} 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 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 getAllNonPrimaryKeyFields(DatabaseTable table) { List 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. */ 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 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(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy