blob: 05bc94595a7d13caafee9345c347d6925ebb2a26 [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.internal.descriptors;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Iterator;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.InheritancePolicy;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.internal.expressions.SQLSelectStatement;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.eclipse.persistence.queries.DataReadQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.ObjectChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
/**
* INTERNAL:
*/
public class CascadeLockingPolicy {
protected Class m_parentClass;
protected ReadObjectQuery m_query;
protected ClassDescriptor m_descriptor;
protected ClassDescriptor m_parentDescriptor;
protected Map<DatabaseField, DatabaseField> m_queryKeyFields;
protected Map<DatabaseField, DatabaseField> m_mappedQueryKeyFields;
protected Map<DatabaseField, DatabaseField> m_unmappedQueryKeyFields;
protected DatabaseMapping m_parentMapping;
protected boolean m_lookForParentMapping;
protected boolean m_shouldHandleUnmappedFields;
protected boolean m_hasCheckedForUnmappedFields;
protected DataReadQuery m_unmappedFieldsQuery;
/**
* INTERNAL:
*/
public CascadeLockingPolicy(ClassDescriptor parentDescriptor, ClassDescriptor descriptor) {
m_descriptor = descriptor;
m_parentDescriptor = parentDescriptor;
m_parentClass = m_parentDescriptor.getJavaClass();
}
/**
* INTERNAL:
*/
protected ReadObjectQuery getQuery() {
if (m_query == null) {
m_query = new ReadObjectQuery(m_parentClass);
Expression selectionCriteria = null;
Iterator keys = m_queryKeyFields.keySet().iterator();
ExpressionBuilder builder = new ExpressionBuilder();
while (keys.hasNext()) {
String keyField = ((DatabaseField) keys.next()).getQualifiedName();
if (selectionCriteria == null) {
selectionCriteria = builder.getField(keyField).equal(builder.getParameter(keyField));
} else {
selectionCriteria.and(builder.getField(keyField).equal(builder.getParameter(keyField)));
}
m_query.addArgument(keyField);
}
m_query.setSelectionCriteria(selectionCriteria);
m_query.setShouldUseWrapperPolicy(false);
}
return m_query;
}
/**
* INTERNAL:
*/
protected DatabaseMapping getParentMapping() {
// If the query is null, then we have not been initialized. Try to
// look up a parent mapping first if we have lookup fields. For a
// 1-M we can not perform the getMappingForField until the fields
// have been initialized.
// If the parent mapping is not found, a query will be initialized
// and the following lookup will no longer hit.
if (m_parentMapping == null && m_lookForParentMapping && m_query == null) {
Iterator<DatabaseField> itFields = m_queryKeyFields.values().iterator();
while(itFields.hasNext()) {
DatabaseMapping mapping = m_descriptor.getObjectBuilder().getMappingForField(itFields.next());
if(mapping == null) {
// at least one field is not mapped therefore no parent mapping exists.
m_parentMapping = null;
break;
} else if(mapping.isObjectReferenceMapping()) {
if(m_parentMapping == null) {
m_parentMapping = mapping;
} else {
if(m_parentMapping != mapping) {
// there's more than one mapping therefore no parent mapping exists.
m_parentMapping = null;
break;
}
}
}
}
}
return m_parentMapping;
}
/**
* Get the descriptor that really represents this object
* In the case of inheritance, the object may represent a subclass of class the descriptor
* represents.
*
* If there is no InheritancePolicy, we return our parentDescriptor
* If there is inheritance we will search for a descriptor that represents parentObj and
* return that descriptor
*/
protected ClassDescriptor getParentDescriptorFromInheritancePolicy(Object parentObj){
ClassDescriptor realParentDescriptor = m_parentDescriptor;
if (realParentDescriptor.hasInheritance()){
InheritancePolicy inheritancePolicy = realParentDescriptor.getInheritancePolicy();
ClassDescriptor childDescriptor = inheritancePolicy.getDescriptor(parentObj.getClass());
if (childDescriptor != null){
realParentDescriptor = childDescriptor;
}
}
return realParentDescriptor;
}
/**
* INTERNAL:
*/
protected AbstractRecord getMappedTranslationRow(Object changedObj, UnitOfWorkImpl uow) {
AbstractRecord translationRow = new DatabaseRecord();
Iterator<Map.Entry<DatabaseField, DatabaseField>> it = m_mappedQueryKeyFields.entrySet().iterator();
while(it.hasNext()) {
Map.Entry<DatabaseField, DatabaseField> entry = it.next();
Object value = m_descriptor.getObjectBuilder().extractValueFromObjectForField(changedObj, entry.getValue(), uow);
translationRow.add(entry.getKey(), value);
}
return translationRow;
}
/**
* INTERNAL:
*/
protected AbstractRecord getUnmappedTranslationRow(Object changedObj, UnitOfWorkImpl uow) {
AbstractRecord unmappedFieldsQueryTranslationRow = new DatabaseRecord();
Iterator<DatabaseField> itPrimaryKey = m_descriptor.getPrimaryKeyFields().iterator();
while (itPrimaryKey.hasNext()) {
DatabaseField primaryKey = itPrimaryKey.next();
Object value = m_descriptor.getObjectBuilder().extractValueFromObjectForField(changedObj, primaryKey, uow);
unmappedFieldsQueryTranslationRow.add(primaryKey, value);
}
List result = (List)uow.executeQuery(m_unmappedFieldsQuery, unmappedFieldsQueryTranslationRow);
if(result == null || result.isEmpty()) {
// the object is not in the db
return null;
}
AbstractRecord unmappedValues = (AbstractRecord)result.get(0);
AbstractRecord translationRow = new DatabaseRecord();
Iterator<Map.Entry<DatabaseField, DatabaseField>> it = m_unmappedQueryKeyFields.entrySet().iterator();
while(it.hasNext()) {
Map.Entry<DatabaseField, DatabaseField> entry = it.next();
Object value = unmappedValues.get(entry.getValue());
translationRow.add(entry.getKey(), value);
}
return translationRow;
}
/**
* INTERNAL:
* Identify mapped and not mapped fields (should be done once).
* The result - either two non-empty Maps m_unmappedQueryKeyFields and m_mappedQueryKeyFields,
* or m_unmappedQueryKeyFields == null and m_mappedQueryKeyFields == m_queryKeyFields.
*/
public void initUnmappedFields(UnitOfWorkImpl uow) {
if(!m_hasCheckedForUnmappedFields) {
m_mappedQueryKeyFields = new HashMap<>();
m_unmappedQueryKeyFields = new HashMap<>();
Iterator<Map.Entry<DatabaseField, DatabaseField>> it = m_queryKeyFields.entrySet().iterator();
while(it.hasNext()) {
Map.Entry<DatabaseField, DatabaseField> entry = it.next();
if(m_descriptor.getObjectBuilder().getMappingForField(entry.getValue()) == null) {
m_unmappedQueryKeyFields.put(entry.getKey(), entry.getValue());
} else {
m_mappedQueryKeyFields.put(entry.getKey(), entry.getValue());
}
}
if(m_unmappedQueryKeyFields.isEmpty()) {
m_unmappedQueryKeyFields = null;
m_mappedQueryKeyFields = m_queryKeyFields;
}
initUnmappedFieldsQuery(uow);
m_hasCheckedForUnmappedFields = true;
}
}
/**
* INTERNAL:
* This method called in case there are m_unmappedQueryKeyFields.
* It creates a query that would fetch the values for this fields from the db.
*/
public void initUnmappedFieldsQuery(UnitOfWorkImpl uow) {
if(m_unmappedFieldsQuery == null) {
m_unmappedFieldsQuery = new DataReadQuery();
Expression whereClause = null;
Expression builder = new ExpressionBuilder();
Iterator<DatabaseField> itPrimaryKey = m_descriptor.getPrimaryKeyFields().iterator();
while (itPrimaryKey.hasNext()) {
DatabaseField primaryKey = itPrimaryKey.next();
Expression expression = builder.getField(primaryKey).equal(builder.getParameter(primaryKey));
whereClause = expression.and(whereClause);
m_unmappedFieldsQuery.addArgument(primaryKey.getQualifiedName());
}
SQLSelectStatement statement = new SQLSelectStatement();
Iterator<DatabaseField> itUnmappedFields = m_unmappedQueryKeyFields.values().iterator();
while (itUnmappedFields.hasNext()) {
DatabaseField field = itUnmappedFields.next();
statement.addField(field);
}
statement.setWhereClause(whereClause);
statement.normalize(uow.getParent(), m_descriptor);
m_unmappedFieldsQuery.setSQLStatement(statement);
m_unmappedFieldsQuery.setSessionName(m_descriptor.getSessionName());
}
}
/**
* INTERNAL:
*/
public void lockNotifyParent(Object obj, UnitOfWorkChangeSet changeSet, UnitOfWorkImpl uow) {
Object parentObj = null;
// Check for a parent object via the parent (back pointer) mapping first.
DatabaseMapping parentMapping = getParentMapping();
if (parentMapping != null && parentMapping.isObjectReferenceMapping()) {
parentObj = parentMapping.getRealAttributeValueFromObject(obj, uow);
}
// If the parent object is still null at this point, try a query.
// check out why no query keys.
if (parentObj == null) {
AbstractRecord translationRow;
if(m_shouldHandleUnmappedFields) {
// should look for unmapped fields.
initUnmappedFields(uow);
if(m_unmappedQueryKeyFields != null) {
// there are some unmapped fields - fetch the values for the from the db.
AbstractRecord unmappedTranslationRow = getUnmappedTranslationRow(obj, uow);
if(unmappedTranslationRow == null) {
// the object is not yet in the db
return;
} else {
// merge mapped and unmapped values into the single translation row.
translationRow = getMappedTranslationRow(obj, uow);
translationRow.putAll(unmappedTranslationRow);
}
} else {
// no unmapped fields
translationRow = getMappedTranslationRow(obj, uow);
}
} else {
// no unmapped fields
translationRow = getMappedTranslationRow(obj, uow);
}
// the query is set to return an unwrapped object.
parentObj = uow.executeQuery(getQuery(), translationRow);
} else {
// make sure the parent object is unwrapped.
if (m_parentDescriptor.hasWrapperPolicy()) {
m_parentDescriptor.getWrapperPolicy().unwrapObject(parentObj, uow);
}
}
ClassDescriptor realParentDescriptor = m_parentDescriptor;
if (parentObj != null){
realParentDescriptor = getParentDescriptorFromInheritancePolicy(parentObj);
}
// If we have a parent object, force update the version field if one
// exists, and keep firing the notification up the chain.
// Otherwise, do nothing.
if (parentObj != null) {
// Need to check if we are a non cascade locking node within a
// cascade locking policy chain.
if (realParentDescriptor.usesOptimisticLocking() && realParentDescriptor.getOptimisticLockingPolicy().isCascaded()) {
ObjectChangeSet ocs = realParentDescriptor.getObjectBuilder().createObjectChangeSet(parentObj, changeSet, uow);
if (!ocs.hasForcedChangesFromCascadeLocking()) {
ocs.setHasForcedChangesFromCascadeLocking(true);
changeSet.addObjectChangeSet(ocs, uow, true);
}
}
// Keep sending the notification up the chain ...
if (realParentDescriptor.hasCascadeLockingPolicies()) {
for (CascadeLockingPolicy policy : realParentDescriptor.getCascadeLockingPolicies()) {
policy.lockNotifyParent(parentObj, changeSet, uow);
}
}
}
}
/**
* INTERNAL:
*/
public void setQueryKeyFields(Map<DatabaseField, DatabaseField> queryKeyFields) {
setQueryKeyFields(queryKeyFields, true);
}
/**
* INTERNAL:
*/
public void setQueryKeyFields(Map<DatabaseField, DatabaseField> queryKeyFields, boolean lookForParentMapping) {
m_queryKeyFields = queryKeyFields;
m_mappedQueryKeyFields = m_queryKeyFields;
this.m_lookForParentMapping = lookForParentMapping;
}
/**
* INTERNAL:
* Indicates whether to expect unmapped fields.
* That should be set to true for UnidirectionalOneToManyMapping.
*/
public void setShouldHandleUnmappedFields(boolean shouldHandleUnmappedFields) {
m_shouldHandleUnmappedFields = shouldHandleUnmappedFields;
}
/**
* INTERNAL:
*/
public boolean shouldHandleUnmappedFields() {
return m_shouldHandleUnmappedFields;
}
}