blob: 8747ee3f62224ded2d13c5b9aa161f8aa32cd2ce [file] [log] [blame]
/*
* 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.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.DatabaseMapping.WriteType;
import org.eclipse.persistence.queries.ObjectLevelModifyQuery;
/**
* <p>
* <b>Purpose</b>: An implementation of the OptimisticLockingPolicy interface.
* This policy compares selected fields in the WHERE clause when doing an update
* or a delete. If any field has been changed, an optimistic locking exception
* will be thrown. Note that the fields specified must be mapped and not be
* primary keys.
* <p>
* NOTE: This policy can only be used inside a unit of work.
*
* @since TopLink 2.5
*/
public class SelectedFieldsLockingPolicy extends FieldsLockingPolicy {
protected Map<DatabaseTable, List<DatabaseField>> lockFieldsByTable;
protected List<DatabaseField> lockFields;
/**
* PUBLIC: Create a new selected fields locking policy. A field locking
* policy is based on locking on the specified 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 SelectedFieldsLockingPolicy() {
super();
this.lockFieldsByTable = new HashMap<>(4);
this.lockFields = new ArrayList<>();
}
/**
* PUBLIC: Add a field name to lock on. All fields in this list will be
* compared when updating if the value of any of the fields does not match
* the value in memory, an OptimisticLockException will be thrown.
*
* Note: An Automatic update will not be done on this field, only a
* comparison occurs.
*/
public void addLockFieldName(String fieldName) {
getLockFields().add(new DatabaseField(fieldName));
}
/**
* INTERNAL: Values to be included in the locking mechanism are added to the
* translation row. For changed fields the normal build row is ok as only
* changed fields matter.
*/
@Override
public void addLockValuesToTranslationRow(ObjectLevelModifyQuery query) throws DatabaseException {
Object object;
verifyUsage(query.getSession());
if (query.isDeleteObjectQuery()) {
object = query.getObject();
} else {
object = query.getBackupClone();
}
// EL bug 319759
if (query.isUpdateObjectQuery()) {
query.setShouldValidateUpdateCallCacheUse(true);
}
for (Iterator<List<DatabaseField>> fields = getLockFieldsByTable().values().iterator(); fields.hasNext();) {
for (DatabaseField field : fields.next()) {
DatabaseMapping mapping = descriptor.getObjectBuilder().getMappingForField(field);
// Bug5892889, Exception will be thrown if no matched database
// field found
if (mapping == null) {
throw DatabaseException.specifiedLockingFieldsNotFoundInDatabase(field.getQualifiedName());
} else {
mapping.writeFromObjectIntoRow(object, query.getTranslationRow(), query.getSession(), WriteType.UNDEFINED);
}
}
}
}
/**
* INTERNAL: returns the lock fields to compare based on the passed in
* table.
*/
@Override
protected List<DatabaseField> getFieldsToCompare(org.eclipse.persistence.internal.helper.DatabaseTable table, AbstractRecord transRow, AbstractRecord modifyRow) {
return getLockFields(table);
}
/**
* INTERNAL: Returns the lock fields
*/
public List<DatabaseField> getLockFields() {
return lockFields;
}
/**
* INTERNAL: returns the lock fields based on the passed in table
*/
protected List<DatabaseField> getLockFields(DatabaseTable table) {
List<DatabaseField> temp = this.lockFieldsByTable.get(table);
if (temp == null) {
return Collections.emptyList();
}
return temp;
}
/**
* INTERNAL: returns the lock fields
*/
protected Map<DatabaseTable, List<DatabaseField>> getLockFieldsByTable() {
return lockFieldsByTable;
}
/**
* INTERNAL: It is responsible for initializing the policy;
*/
@Override
public void initialize(AbstractSession session) {
super.initialize(session);
List<DatabaseField> lockFields = getLockFields();
int size = lockFields.size();
for (int index = 0; index < size; index++) {
DatabaseField field = lockFields.get(index);
field = descriptor.buildField(field);
lockFields.set(index, field);
List<DatabaseField> fieldsForTable = getLockFieldsByTable().get(field.getTable());
if (fieldsForTable == null) {
fieldsForTable = new ArrayList<>();
getLockFieldsByTable().put(field.getTable(), fieldsForTable);
}
fieldsForTable.add(field);
}
}
/**
* PUBLIC: Set the field names to lock on. All fields in this list will be
* compared when Updating. If the value of any of the fields does not match
* the value in memory, an OptimisticLockException will be thrown.
*
* Note: An Automatic update will not be done on this field, only a
* comparison occurs.
*/
public void setLockFieldNames(List<String> lockFieldNames) {
for (String name : lockFieldNames) {
addLockFieldName(name);
}
}
/**
* INTERNAL: Sets the lock fields
*/
protected void setLockFields(List<DatabaseField> lockFields) {
this.lockFields = lockFields;
}
/**
* INTERNAL: Used to set the field names to be used in this policy.
*/
protected void setLockFieldsByTable(Map<DatabaseTable, List<DatabaseField>> lockFieldsByTable) {
this.lockFieldsByTable = lockFieldsByTable;
}
}