| /* |
| * Copyright (c) 1998, 2019 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 |
| * @param parentObj |
| * @return |
| */ |
| 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; |
| } |
| } |