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

org.eclipse.persistence.mappings.OneToManyMapping Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1998, 2024 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
//     07/19/2011-2.2.1 Guy Pelletier
//       - 338812: ManyToMany mapping in aggregate object violate integrity constraint on deletion
package org.eclipse.persistence.mappings;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.OptimisticLockException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.internal.descriptors.CascadeLockingPolicy;
import org.eclipse.persistence.internal.descriptors.ObjectBuilder;
import org.eclipse.persistence.internal.expressions.FieldExpression;
import org.eclipse.persistence.internal.expressions.ParameterExpression;
import org.eclipse.persistence.internal.expressions.SQLUpdateStatement;
import org.eclipse.persistence.internal.helper.ConversionManager;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.identitymaps.CacheId;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.internal.queries.ContainerPolicy;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.ObjectChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.mappings.foundation.MapComponentMapping;
import org.eclipse.persistence.queries.DataModifyQuery;
import org.eclipse.persistence.queries.DeleteAllQuery;
import org.eclipse.persistence.queries.DeleteObjectQuery;
import org.eclipse.persistence.queries.InsertObjectQuery;
import org.eclipse.persistence.queries.ModifyQuery;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.eclipse.persistence.queries.ObjectLevelModifyQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.WriteObjectQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 

Purpose: This mapping is used to represent the * typical RDBMS relationship between a single * source object and collection of target objects; where, * on the database, the target objects have references * (foreign keys) to the source object. * * @author Sati * @since TOPLink/Java 1.0 */ public class OneToManyMapping extends CollectionMapping implements RelationalMapping, MapComponentMapping { /** Used for data modification events. */ protected static final String PostInsert = "postInsert"; protected static final String ObjectRemoved = "objectRemoved"; protected static final String ObjectAdded = "objectAdded"; /** The target foreign key fields that reference the sourceKeyFields. */ protected List targetForeignKeyFields; /** The (typically primary) source key fields that are referenced by the targetForeignKeyFields. */ protected List sourceKeyFields; /** This maps the target foreign key fields to the corresponding (primary) source key fields. */ protected transient Map targetForeignKeysToSourceKeys; /** This maps the (primary) source key fields to the corresponding target foreign key fields. */ protected transient Map sourceKeysToTargetForeignKeys; /** All targetForeignKeyFields should have the same table. * Used only in case data modification events required. **/ protected transient DatabaseTable targetForeignKeyTable; /** Primary keys of targetForeignKeyTable: * the same as referenceDescriptor().getPrimaryKeyFields() in case the table is default table of reference descriptor; * otherwise contains secondary table's primary key fields in the same order as default table primary keys mapped to them. * Used only in case data modification events required. **/ protected transient List targetPrimaryKeyFields; /** * Keep a reference to the source and target expressions to post initialize * when building a selection criteria early. */ protected transient List sourceExpressionsToPostInitialize; protected transient List targetExpressionsToPostInitialize; /** * Query used to update a single target row setting its foreign key to point to the source. * Run once for each target added to the source. * Example: * for Employee with managedEmployees attribute mapped with UnidirectionalOneToMany * the query looks like: * UPDATE EMPLOYEE SET MANAGER_ID = 1 WHERE (EMP_ID = 2) * where 1 is id of the source, and 2 is the id of the target to be added. * Used only in case data modification events required. **/ protected DataModifyQuery addTargetQuery; protected boolean hasCustomAddTargetQuery; protected Boolean shouldDeferInserts = null; /** * Query used to update a single target row changing its foreign key value from the one pointing to the source to null. * Run once for each target removed from the source. * Example: * for Employee with managedEmployees attribute mapped with UnidirectionalOneToMany * the query looks like: * UPDATE EMPLOYEE SET MANAGER_ID = null WHERE ((MANAGER_ID = 1) AND (EMP_ID = 2)) * where 1 is id of the source, and 2 is the id of the target to be removed. * Used only in case data modification events required. **/ protected DataModifyQuery removeTargetQuery; protected boolean hasCustomRemoveTargetQuery; /** * Query used to update all target rows changing target foreign key value from the one pointing to the source to null. * Run before the source object is deleted. * Example: * for Employee with managedEmployees attribute mapped with UnidirectionalOneToMany * the query looks like: * UPDATE EMPLOYEE SET MANAGER_ID = null WHERE (MANAGER_ID = 1) * where 1 is id of the source to be deleted. * Used only in case data modification events required. **/ protected DataModifyQuery removeAllTargetsQuery; protected boolean hasCustomRemoveAllTargetsQuery; /** * PUBLIC: * Default constructor. */ public OneToManyMapping() { super(); this.targetForeignKeysToSourceKeys = new HashMap<>(2); this.sourceKeysToTargetForeignKeys = new HashMap<>(2); this.sourceKeyFields = new ArrayList<>(1); this.targetForeignKeyFields = new ArrayList<>(1); this.sourceExpressionsToPostInitialize = new CopyOnWriteArrayList<>(); this.targetExpressionsToPostInitialize = new CopyOnWriteArrayList<>(); this.deleteAllQuery = new DeleteAllQuery(); this.removeTargetQuery = new DataModifyQuery(); this.removeAllTargetsQuery = new DataModifyQuery(); this.isListOrderFieldSupported = true; } /** * INTERNAL: */ @Override public boolean isRelationalMapping() { return true; } /** * INTERNAL: * Add the associated fields to the appropriate collections. */ @Override public void addTargetForeignKeyField(DatabaseField targetForeignKeyField, DatabaseField sourceKeyField) { getTargetForeignKeyFields().add(targetForeignKeyField); getSourceKeyFields().add(sourceKeyField); } /** * PUBLIC: * Define the target foreign key relationship in the one-to-many mapping. * This method is used for composite target foreign key relationships. * That is, the target object's table has multiple foreign key fields * that are references to * the source object's (typically primary) key fields. * Both the target foreign key field name and the corresponding * source primary key field name must be specified. * Because the target object's table must store a foreign key to the source table, * the target object must map that foreign key, this is normally done through a * one-to-one mapping back-reference. Other options include: *

    *
  • use a DirectToFieldMapping and maintain the * foreign key fields directly in the target *
  • use a ManyToManyMapping *
  • use an AggregateCollectionMapping *
* @see DirectToFieldMapping * @see ManyToManyMapping * @see AggregateCollectionMapping */ public void addTargetForeignKeyFieldName(String targetForeignKeyFieldName, String sourceKeyFieldName) { addTargetForeignKeyField(new DatabaseField(targetForeignKeyFieldName), new DatabaseField(sourceKeyFieldName)); } /** * INTERNAL: * Verifies listOrderField's table: it must be the same table that contains all target foreign keys. * Precondition: listOrderField != null. */ @Override protected void buildListOrderField() { if(this.listOrderField.hasTableName()) { if(!this.targetForeignKeyTable.equals(this.listOrderField.getTable())) { throw DescriptorException.listOrderFieldTableIsWrong(this.getDescriptor(), this, this.listOrderField.getTable(), this.targetForeignKeyTable); } } else { listOrderField.setTable(this.targetForeignKeyTable); } this.listOrderField = this.getReferenceDescriptor().buildField(this.listOrderField, this.targetForeignKeyTable); } /** * The selection criteria are created with target foreign keys and source "primary" keys. * These criteria are then used to read the target records from the table. * These criteria are also used as the default "delete all" criteria. *

* CR#3922 - This method is almost the same as buildSelectionCriteria() the difference * is that TargetForeignKeysToSourceKeys contains more information after login then SourceKeyFields * contains before login. */ protected Expression buildDefaultSelectionCriteriaAndAddFieldsToQuery() { Expression selectionCriteria = null; Expression builder = new ExpressionBuilder(); for (Iterator keys = getTargetForeignKeysToSourceKeys().keySet().iterator(); keys.hasNext();) { DatabaseField targetForeignKey = keys.next(); DatabaseField sourceKey = getTargetForeignKeysToSourceKeys().get(targetForeignKey); Expression partialSelectionCriteria = builder.getField(targetForeignKey).equal(builder.getParameter(sourceKey)); selectionCriteria = partialSelectionCriteria.and(selectionCriteria); } getContainerPolicy().addAdditionalFieldsToQuery(getSelectionQuery(), builder); return selectionCriteria; } /** * This method would allow customers to get the potential selection criteria for a mapping * prior to initialization. This would allow them to more easily create an amendment method * that would amend the SQL for the join. *

* CR#3922 - This method is almost the same as buildDefaultSelectionCriteria() the difference * is that TargetForeignKeysToSourceKeys contains more information after login then SourceKeyFields * contains before login. */ public Expression buildSelectionCriteria() { //CR3922 Expression selectionCriteria = null; Expression builder = new ExpressionBuilder(); Iterator iterator1 = getSourceKeyFields().iterator(); for (Iterator iterator = getTargetForeignKeyFields().iterator(); iterator.hasNext();) { DatabaseField targetForeignKey = iterator.next(); DatabaseField sourceKey = iterator1.next(); Expression targetExpression = builder.getField(targetForeignKey); Expression sourceExpression = builder.getParameter(sourceKey); // store the expressions in order to initialize their fields later this.targetExpressionsToPostInitialize.add(targetExpression); this.sourceExpressionsToPostInitialize.add(sourceExpression); Expression partialSelectionCriteria = targetExpression.equal(sourceExpression); selectionCriteria = partialSelectionCriteria.and(selectionCriteria); } return selectionCriteria; } /** * INTERNAL: * This method is used to store the FK fields that can be cached that correspond to noncacheable mappings * the FK field values will be used to re-issue the query when cloning the shared cache entity */ @Override public void collectQueryParameters(Set cacheFields){ cacheFields.addAll(getSourceKeyFields()); } /** * INTERNAL: * Clone the appropriate attributes. */ @Override public Object clone() { OneToManyMapping clone = (OneToManyMapping)super.clone(); Map old2cloned = new HashMap<>(); clone.sourceKeyFields = cloneDatabaseFieldList(sourceKeyFields, old2cloned); clone.targetForeignKeyFields = cloneDatabaseFieldList(targetForeignKeyFields, old2cloned); clone.setTargetForeignKeysToSourceKeys(cloneKeysMap(getTargetForeignKeysToSourceKeys(), old2cloned)); clone.sourceKeysToTargetForeignKeys = cloneKeysMap(getSourceKeysToTargetForeignKeys(), old2cloned); if (addTargetQuery != null){ clone.addTargetQuery = (DataModifyQuery) this.addTargetQuery.clone(); } clone.removeTargetQuery = (DataModifyQuery) this.removeTargetQuery.clone(); clone.removeAllTargetsQuery = (DataModifyQuery) this.removeAllTargetsQuery.clone(); return clone; } private Map cloneKeysMap(Map toClone, Map old2cloned) { if (toClone == null) { return null; } Map cloneTarget2Src = new HashMap<>(toClone.size()); for (Map.Entry e : toClone.entrySet()) { cloneTarget2Src.put(old2cloned.get(e.getKey()), old2cloned.get(e.getValue())); } return cloneTarget2Src; } private List cloneDatabaseFieldList(List oldFlds, Map old2cloned) { List clonedSourceKeyFields = null; if (oldFlds != null) { clonedSourceKeyFields = new ArrayList<>(oldFlds.size()); for (DatabaseField old : oldFlds) { DatabaseField cf = old.clone(); clonedSourceKeyFields.add(cf); old2cloned.put(old, cf); } } return clonedSourceKeyFields; } /** * INTERNAL * Called when a DatabaseMapping is used to map the key in a collection. Returns the key. */ @Override public Object createMapComponentFromRow(AbstractRecord dbRow, ObjectBuildingQuery query, CacheKey parentCacheKey, AbstractSession session, boolean isTargetProtected){ return session.executeQuery(getSelectionQuery(), dbRow); } /** * Delete all the reference objects with a single query. */ protected void deleteAll(DeleteObjectQuery query, AbstractSession session) throws DatabaseException { Object attribute = getAttributeValueFromObject(query.getObject()); if (usesIndirection()) { if (!this.indirectionPolicy.objectIsInstantiated(attribute)) { // An empty Vector indicates to DeleteAllQuery that no objects should be removed from cache ((DeleteAllQuery)this.deleteAllQuery).executeDeleteAll(session.getSessionForClass(this.referenceClass), query.getTranslationRow(), new Vector(0)); return; } } Object referenceObjects = getRealCollectionAttributeValueFromObject(query.getObject(), session); // PERF: Avoid delete if empty. if (session.isUnitOfWork() && this.containerPolicy.isEmpty(referenceObjects)) { return; } ((DeleteAllQuery)this.deleteAllQuery).executeDeleteAll(session.getSessionForClass(getReferenceClass()), query.getTranslationRow(), this.containerPolicy.vectorFor(referenceObjects, session)); } /** * This method will make sure that all the records privately owned by this mapping are * actually removed. If such records are found then those are all read and removed one * by one along with their privately owned parts. */ protected void deleteReferenceObjectsLeftOnDatabase(DeleteObjectQuery query) throws DatabaseException, OptimisticLockException { Object objects = readPrivateOwnedForObject(query); // Delete all these object one by one. ContainerPolicy cp = getContainerPolicy(); for (Object iter = cp.iteratorFor(objects); cp.hasNext(iter);) { query.getSession().deleteObject(cp.next(iter, query.getSession())); } } /** * INTERNAL: * Extract the source primary key value from the target row. * Used for batch reading, most following same order and fields as in the mapping. */ @Override protected Object extractKeyFromTargetRow(AbstractRecord row, AbstractSession session) { int size = this.sourceKeyFields.size(); Object[] key = new Object[size]; ConversionManager conversionManager = session.getDatasourcePlatform().getConversionManager(); for (int index = 0; index < size; index++) { DatabaseField targetField = this.targetForeignKeyFields.get(index); DatabaseField sourceField = this.sourceKeyFields.get(index); Object value = row.get(targetField); // Must ensure the classification gets a cache hit. try { value = conversionManager.convertObject(value, sourceField.getType()); } catch (ConversionException e) { throw ConversionException.couldNotBeConverted(this, getDescriptor(), e); } key[index] = value; } return new CacheId(key); } /** * Extract the key field values from the specified row. * Used for batch reading. Keep the fields in the same order * as in the targetForeignKeysToSourceKeys map. */ @Override protected Object extractBatchKeyFromRow(AbstractRecord row, AbstractSession session) { int size = this.sourceKeyFields.size(); Object[] key = new Object[size]; ConversionManager conversionManager = session.getDatasourcePlatform().getConversionManager(); for (int index = 0; index < size; index++) { DatabaseField sourceField = this.sourceKeyFields.get(index); Object value = row.get(sourceField); // Must ensure the classification to get a cache hit. try { value = conversionManager.convertObject(value, sourceField.getType()); } catch (ConversionException exception) { throw ConversionException.couldNotBeConverted(this, this.descriptor, exception); } key[index] = value; } return new CacheId(key); } /** * Overrides CollectionMapping because this mapping requires a DeleteAllQuery instead of a ModifyQuery. */ @Override protected ModifyQuery getDeleteAllQuery() { if (deleteAllQuery == null) { deleteAllQuery = new DeleteAllQuery();//this is casted to a DeleteAllQuery } return deleteAllQuery; } /** * INTERNAL: * Return source key fields for translation by an AggregateObjectMapping */ @Override public Collection getFieldsForTranslationInAggregate() { return getSourceKeyFields(); } /** * PUBLIC: * Return the source key field names associated with the mapping. * These are in-order with the targetForeignKeyFieldNames. */ public List getSourceKeyFieldNames() { List fieldNames = new ArrayList<>(getSourceKeyFields().size()); for (Iterator iterator = getSourceKeyFields().iterator(); iterator.hasNext();) { fieldNames.add(iterator.next().getQualifiedName()); } return fieldNames; } /** * INTERNAL: * Return the source key fields. */ public List getSourceKeyFields() { return sourceKeyFields; } /** * INTERNAL: * Return the source/target key fields. */ public Map getSourceKeysToTargetForeignKeys() { if (sourceKeysToTargetForeignKeys == null) { sourceKeysToTargetForeignKeys = new HashMap<>(2); } return sourceKeysToTargetForeignKeys; } /** * INTERNAL: * Primary keys of targetForeignKeyTable. */ @Override public List getTargetPrimaryKeyFields() { return this.targetPrimaryKeyFields; } /** * INTERNAL: * Return the target foreign key field names associated with the mapping. * These are in-order with the targetForeignKeyFieldNames. */ public List getTargetForeignKeyFieldNames() { List fieldNames = new ArrayList<>(getTargetForeignKeyFields().size()); for (Iterator iterator = getTargetForeignKeyFields().iterator(); iterator.hasNext();) { fieldNames.add(iterator.next().getQualifiedName()); } return fieldNames; } /** * INTERNAL: * Return the target foreign key fields. */ public List getTargetForeignKeyFields() { return targetForeignKeyFields; } /** * INTERNAL: * Return the target/source key fields. */ public Map getTargetForeignKeysToSourceKeys() { if (targetForeignKeysToSourceKeys == null) { targetForeignKeysToSourceKeys = new HashMap<>(2); } return targetForeignKeysToSourceKeys; } /** * INTERNAL: * Return whether the mapping has any inverse constraint dependencies, * such as foreign keys and join tables. */ @Override public boolean hasInverseConstraintDependency() { return true; } /** * INTERNAL: * Initialize the mapping. */ @Override public void initialize(AbstractSession session) throws DescriptorException { if (session.hasBroker()) { if (getReferenceClass() == null) { throw DescriptorException.referenceClassNotSpecified(this); } // substitute session that owns the mapping for the session that owns reference descriptor. session = session.getBroker().getSessionForClass(getReferenceClass()); } super.initialize(session); getContainerPolicy().initialize(session, getReferenceDescriptor().getDefaultTable()); if (shouldInitializeSelectionCriteria()) { setSelectionCriteria(buildDefaultSelectionCriteriaAndAddFieldsToQuery()); } initializeDeleteAllQuery(session); if (requiresDataModificationEvents() || getContainerPolicy().requiresDataModificationEvents()) { initializeAddTargetQuery(session); initializeRemoveTargetQuery(session); initializeRemoveAllTargetsQuery(session); } // Check if any foreign keys reference a secondary table. if (getDescriptor().getTables().size() > 1) { DatabaseTable firstTable = getDescriptor().getTables().get(0); for (DatabaseField field : getSourceKeyFields()) { if (!field.getTable().equals(firstTable)) { getDescriptor().setHasMultipleTableConstraintDependecy(true); } } } } /** * INTERNAL: * Initialize addTargetQuery. */ protected void initializeAddTargetQuery(AbstractSession session) { AbstractRecord modifyRow = createModifyRowForAddTargetQuery(); if(modifyRow.isEmpty()) { return; } if (!hasCustomAddTargetQuery){ addTargetQuery = new DataModifyQuery(); } if (!addTargetQuery.hasSessionName()) { addTargetQuery.setSessionName(session.getName()); } if (hasCustomAddTargetQuery) { return; } // all fields in modifyRow must have the same table DatabaseTable table = (modifyRow.getFields().get(0)).getTable(); // Build where clause expression. Expression whereClause = null; Expression builder = new ExpressionBuilder(); int size = targetPrimaryKeyFields.size(); for (int index = 0; index < size; index++) { DatabaseField targetPrimaryKey = targetPrimaryKeyFields.get(index); Expression expression = builder.getField(targetPrimaryKey).equal(builder.getParameter(targetPrimaryKey)); whereClause = expression.and(whereClause); } SQLUpdateStatement statement = new SQLUpdateStatement(); statement.setTable(table); statement.setWhereClause(whereClause); statement.setModifyRow(modifyRow); addTargetQuery.setSQLStatement(statement); } /** * INTERNAL: */ protected AbstractRecord createModifyRowForAddTargetQuery() { AbstractRecord modifyRow = new DatabaseRecord(); containerPolicy.addFieldsForMapKey(modifyRow); if(listOrderField != null) { modifyRow.add(listOrderField, null); } return modifyRow; } /** * INTERNAL: * Initialize changeOrderTargetQuery. */ @Override protected void initializeChangeOrderTargetQuery(AbstractSession session) { boolean hasChangeOrderTargetQuery = changeOrderTargetQuery != null; if(!hasChangeOrderTargetQuery) { changeOrderTargetQuery = new DataModifyQuery(); } changeOrderTargetQuery = new DataModifyQuery(); if (!changeOrderTargetQuery.hasSessionName()) { changeOrderTargetQuery.setSessionName(session.getName()); } if (hasChangeOrderTargetQuery) { return; } DatabaseTable table = this.listOrderField.getTable(); // Build where clause expression. Expression whereClause = null; Expression builder = new ExpressionBuilder(); int size = targetPrimaryKeyFields.size(); for (int index = 0; index < size; index++) { DatabaseField targetPrimaryKey = targetPrimaryKeyFields.get(index); Expression expression = builder.getField(targetPrimaryKey).equal(builder.getParameter(targetPrimaryKey)); whereClause = expression.and(whereClause); } AbstractRecord modifyRow = new DatabaseRecord(); modifyRow.add(this.listOrderField, null); SQLUpdateStatement statement = new SQLUpdateStatement(); statement.setTable(table); statement.setWhereClause(whereClause); statement.setModifyRow(modifyRow); changeOrderTargetQuery.setSQLStatement(statement); } /** * Initialize the delete all query. * This query is used to delete the collection of objects from the * database. */ protected void initializeDeleteAllQuery(AbstractSession session) { ((DeleteAllQuery)getDeleteAllQuery()).setReferenceClass(getReferenceClass()); getDeleteAllQuery().setName(getAttributeName()); ((DeleteAllQuery)getDeleteAllQuery()).setIsInMemoryOnly(isCascadeOnDeleteSetOnDatabase()); if (!hasCustomDeleteAllQuery()) { // the selection criteria are re-used by the delete all query if (getSelectionCriteria() == null) { getDeleteAllQuery().setSelectionCriteria(buildDefaultSelectionCriteriaAndAddFieldsToQuery()); } else { getDeleteAllQuery().setSelectionCriteria(getSelectionCriteria()); } } if (!getDeleteAllQuery().hasSessionName()) { getDeleteAllQuery().setSessionName(session.getName()); } if (getDeleteAllQuery().getPartitioningPolicy() == null) { getDeleteAllQuery().setPartitioningPolicy(getPartitioningPolicy()); } } /** * INTERNAL: * Initialize targetForeignKeyTable and initializeTargetPrimaryKeyFields. * This method should be called after initializeTargetForeignKeysToSourceKeys method, * which creates targetForeignKeyFields (guaranteed to be not empty in case * requiresDataModificationEvents method returns true - the only case for the method to be called). */ protected void initializeTargetPrimaryKeyFields() { // all target foreign key fields must have the same table. int size = getTargetForeignKeyFields().size(); HashSet tables = new HashSet<>(); for(int i=0; i < size; i++) { tables.add(getTargetForeignKeyFields().get(i).getTable()); } if(tables.size() == 1) { this.targetForeignKeyTable = getTargetForeignKeyFields().get(0).getTable(); } else { // multiple foreign key tables - throw exception. throw DescriptorException.multipleTargetForeignKeyTables(this.getDescriptor(), this, tables); } List defaultTablePrimaryKeyFields = getReferenceDescriptor().getPrimaryKeyFields(); if(this.targetForeignKeyTable.equals(getReferenceDescriptor().getDefaultTable())) { this.targetPrimaryKeyFields = defaultTablePrimaryKeyFields; } else { int sizePk = defaultTablePrimaryKeyFields.size(); this.targetPrimaryKeyFields = new ArrayList<>(); for(int i=0; i < sizePk; i++) { this.targetPrimaryKeyFields.add(null); } Map map = getReferenceDescriptor().getAdditionalTablePrimaryKeyFields().get(this.targetForeignKeyTable); Iterator> it = map.entrySet().iterator(); while(it.hasNext()) { Map.Entry entry = it.next(); DatabaseField sourceField = entry.getKey(); DatabaseField targetField = entry.getValue(); DatabaseField additionalTableField; DatabaseField defaultTableField; if(sourceField.getTable().equals(this.targetForeignKeyTable)) { additionalTableField = sourceField; defaultTableField = targetField; } else { defaultTableField = sourceField; additionalTableField = targetField; } int index = defaultTablePrimaryKeyFields.indexOf(defaultTableField); getReferenceDescriptor().buildField(additionalTableField, this.targetForeignKeyTable); this.targetPrimaryKeyFields.set(index, additionalTableField); } } } /** * INTERNAL: * Initialize removeTargetQuery. */ protected void initializeRemoveTargetQuery(AbstractSession session) { if (!removeTargetQuery.hasSessionName()) { removeTargetQuery.setSessionName(session.getName()); } if (hasCustomRemoveTargetQuery) { return; } // All targetForeignKeys should have the same table DatabaseTable table = targetForeignKeyFields.get(0).getTable(); // Build where clause expression. Expression whereClause = null; Expression builder = new ExpressionBuilder(); int size = targetPrimaryKeyFields.size(); for (int index = 0; index < size; index++) { DatabaseField targetPrimaryKey = targetPrimaryKeyFields.get(index); Expression expression = builder.getField(targetPrimaryKey).equal(builder.getParameter(targetPrimaryKey)); whereClause = expression.and(whereClause); } AbstractRecord modifyRow = new DatabaseRecord(); if(shouldRemoveTargetQueryModifyTargetForeignKey()) { size = targetForeignKeyFields.size(); for (int index = 0; index < size; index++) { DatabaseField targetForeignKey = targetForeignKeyFields.get(index); modifyRow.put(targetForeignKey, null); Expression expression = builder.getField(targetForeignKey).equal(builder.getParameter(targetForeignKey)); whereClause = expression.and(whereClause); } } if(listOrderField != null) { modifyRow.add(listOrderField, null); } SQLUpdateStatement statement = new SQLUpdateStatement(); statement.setTable(table); statement.setWhereClause(whereClause); statement.setModifyRow(modifyRow); removeTargetQuery.setSQLStatement(statement); } /** * Initialize and set the descriptor for the referenced class in this mapping. * Added here initialization of target foreign keys and target primary keys so that they are ready when * CollectionMapping.initialize initializes listOrderField. */ @Override protected void initializeReferenceDescriptor(AbstractSession session) throws DescriptorException { super.initializeReferenceDescriptor(session); if (!isSourceKeySpecified()) { // sourceKeyFields will be empty when #setTargetForeignKeyFieldName() is used setSourceKeyFields(new ArrayList<>(getDescriptor().getPrimaryKeyFields())); } initializeTargetForeignKeysToSourceKeys(); if (usesIndirection()) { for (DatabaseField field : getSourceKeyFields()) { field.setKeepInRow(true); } } if(requiresDataModificationEvents() || getContainerPolicy().requiresDataModificationEvents()) { initializeTargetPrimaryKeyFields(); } } /** * INTERNAL: * Initialize removeAllTargetsQuery. */ protected void initializeRemoveAllTargetsQuery(AbstractSession session) { if (!removeAllTargetsQuery.hasSessionName()) { removeAllTargetsQuery.setSessionName(session.getName()); } if (hasCustomRemoveAllTargetsQuery) { return; } // All targetForeignKeys should have the same table DatabaseTable table = targetForeignKeyFields.get(0).getTable(); // Build where clause expression. Expression whereClause = null; Expression builder = new ExpressionBuilder(); AbstractRecord modifyRow = new DatabaseRecord(); int size = targetForeignKeyFields.size(); for (int index = 0; index < size; index++) { DatabaseField targetForeignKey = targetForeignKeyFields.get(index); if(shouldRemoveTargetQueryModifyTargetForeignKey()) { modifyRow.put(targetForeignKey, null); } Expression expression = builder.getField(targetForeignKey).equal(builder.getParameter(targetForeignKey)); whereClause = expression.and(whereClause); } if(this.listOrderField != null) { // targetForeignKeys and listOrderField should have the same table modifyRow.add(this.listOrderField, null); } SQLUpdateStatement statement = new SQLUpdateStatement(); statement.setTable(table); statement.setWhereClause(whereClause); statement.setModifyRow(modifyRow); removeAllTargetsQuery.setSQLStatement(statement); } /** * Verify, munge, and hash the target foreign keys and source keys. */ protected void initializeTargetForeignKeysToSourceKeys() throws DescriptorException { if (getTargetForeignKeyFields().isEmpty()) { if (shouldInitializeSelectionCriteria() || requiresDataModificationEvents() || getContainerPolicy().requiresDataModificationEvents()) { throw DescriptorException.noTargetForeignKeysSpecified(this); } else { // if they have specified selection criteria, the keys do not need to be specified return; } } if (getTargetForeignKeyFields().size() != getSourceKeyFields().size()) { throw DescriptorException.targetForeignKeysSizeMismatch(this); } for (int index = 0; index < getTargetForeignKeyFields().size(); index++) { DatabaseField field = getReferenceDescriptor().buildField(getTargetForeignKeyFields().get(index)); getTargetForeignKeyFields().set(index, field); } for (int index = 0; index < getSourceKeyFields().size(); index++) { DatabaseField field = getDescriptor().buildField(getSourceKeyFields().get(index)); getSourceKeyFields().set(index, field); } Iterator targetForeignKeys = getTargetForeignKeyFields().iterator(); Iterator sourceKeys = getSourceKeyFields().iterator(); while (targetForeignKeys.hasNext()) { DatabaseField targetForeignKey = targetForeignKeys.next(); DatabaseField sourcePrimaryKey = sourceKeys.next(); getTargetForeignKeysToSourceKeys().put(targetForeignKey, sourcePrimaryKey); getSourceKeysToTargetForeignKeys().put(sourcePrimaryKey, targetForeignKey); } } /** * INTERNAL: */ @Override public boolean isOneToManyMapping() { return true; } /** * Return whether the source key is specified. * It will be empty when #setTargetForeignKeyFieldName(String) is used. */ protected boolean isSourceKeySpecified() { return !getSourceKeyFields().isEmpty(); } /** * INTERNAL: * An object was added to the collection during an update, insert it if private. */ @Override protected void objectAddedDuringUpdate(ObjectLevelModifyQuery query, Object objectAdded, ObjectChangeSet changeSet, Map extraData) throws DatabaseException, OptimisticLockException { // First insert/update object. super.objectAddedDuringUpdate(query, objectAdded, changeSet, extraData); if (requiresDataModificationEvents() || containerPolicy.requiresDataModificationEvents()){ // In the uow data queries are cached until the end of the commit. if (query.shouldCascadeOnlyDependentParts()) { if (shouldDeferInsert()) { // Hey I might actually want to use an inner class here... ok array for now. Object[] event = new Object[4]; event[0] = ObjectAdded; event[1] = query; event[2] = objectAdded; event[3] = extraData; query.getSession().getCommitManager().addDataModificationEvent(this, event); } else { ContainerPolicy cp = getContainerPolicy(); prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getDescriptor(), query.getSession()); AbstractRecord keyRow = buildKeyRowForTargetUpdate(query); // Extract target field and its value. Construct insert statement and execute it ClassDescriptor referenceDesc = getReferenceDescriptor(); AbstractSession session = query.getSession(); AbstractRecord databaseRow = referenceDesc.getObjectBuilder().buildRow(keyRow, objectAdded, session, WriteType.INSERT); ContainerPolicy.copyMapDataToRow(cp.getKeyMappingDataForWriteQuery(objectAdded, query.getSession()), databaseRow); if(listOrderField != null && extraData != null) { databaseRow.put(listOrderField, extraData.get(listOrderField)); } InsertObjectQuery insertQuery = getInsertObjectQuery(session, referenceDesc); insertQuery.setObject(objectAdded); insertQuery.setCascadePolicy(query.getCascadePolicy()); insertQuery.setTranslationRow(databaseRow); insertQuery.setModifyRow(databaseRow); insertQuery.setIsPrepared(false); query.getSession().executeQuery(insertQuery); } } else { updateTargetForeignKeyPostUpdateSource_ObjectAdded(query, objectAdded, extraData); } } } /** * INTERNAL: * An object was removed to the collection during an update, delete it if private. */ @Override protected void objectRemovedDuringUpdate(ObjectLevelModifyQuery query, Object objectDeleted, Map extraData) throws DatabaseException, OptimisticLockException { if(!isPrivateOwned()) { if (requiresDataModificationEvents() || containerPolicy.requiresDataModificationEvents()){ // In the uow data queries are cached until the end of the commit. if (query.shouldCascadeOnlyDependentParts()) { // Hey I might actually want to use an inner class here... ok array for now. Object[] event = new Object[3]; event[0] = ObjectRemoved; event[1] = query; event[2] = objectDeleted; query.getSession().getCommitManager().addDataModificationEvent(this, event); } else { updateTargetForeignKeyPostUpdateSource_ObjectRemoved(query, objectDeleted); } } } // Delete object after join entry is delete if private. super.objectRemovedDuringUpdate(query, objectDeleted, extraData); } /** * INTERNAL: * Perform the commit event. * This is used in the uow to delay data modifications. */ @Override public void performDataModificationEvent(Object[] event, AbstractSession session) throws DatabaseException, DescriptorException { // Hey I might actually want to use an inner class here... ok array for now. if (event[0] == PostInsert) { updateTargetRowPostInsertSource((WriteObjectQuery)event[1]); } else if (event[0] == ObjectRemoved) { updateTargetForeignKeyPostUpdateSource_ObjectRemoved((WriteObjectQuery)event[1], event[2]); } else if (event[0] == ObjectAdded) { updateTargetForeignKeyPostUpdateSource_ObjectAdded((WriteObjectQuery)event[1], event[2], (Map)event[3]); } else { throw DescriptorException.invalidDataModificationEventCode(event[0], this); } } /** * INTERNAL: * Insert the reference objects. */ @Override public void postInsert(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { if (isReadOnly()) { return; } if (shouldObjectModifyCascadeToParts(query) && !query.shouldCascadeOnlyDependentParts()) { Object objects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession()); // insert each object one by one ContainerPolicy cp = getContainerPolicy(); for (Object iter = cp.iteratorFor(objects); cp.hasNext(iter);) { Object wrappedObject = cp.nextEntry(iter, query.getSession()); Object object = cp.unwrapIteratorResult(wrappedObject); if (isPrivateOwned()) { // no need to set changeSet as insert is a straight copy InsertObjectQuery insertQuery = new InsertObjectQuery(); insertQuery.setIsExecutionClone(true); insertQuery.setObject(object); insertQuery.setCascadePolicy(query.getCascadePolicy()); query.getSession().executeQuery(insertQuery); } else { // This will happen in a cascaded query. // This is done only for persistence by reachability and is not required if the targets are in the queue anyway // Avoid cycles by checking commit manager, this is allowed because there is no dependency. if (!query.getSession().getCommitManager().isCommitInPreModify(object)) { WriteObjectQuery writeQuery = new WriteObjectQuery(); writeQuery.setIsExecutionClone(true); writeQuery.setObject(object); writeQuery.setCascadePolicy(query.getCascadePolicy()); query.getSession().executeQuery(writeQuery); } } cp.propogatePostInsert(query, wrappedObject); } } if (requiresDataModificationEvents() || getContainerPolicy().requiresDataModificationEvents()) { // only cascade dependents in UOW if (query.shouldCascadeOnlyDependentParts()) { if (requiresDataModificationEvents() || containerPolicy.shouldUpdateForeignKeysPostInsert()) { if (shouldDeferInsert()) { // Hey I might actually want to use an inner class here... ok array for now. Object[] event = new Object[2]; event[0] = PostInsert; event[1] = query; query.getSession().getCommitManager().addDataModificationEvent(this, event); } else { ContainerPolicy cp = getContainerPolicy(); Object objects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession()); if (cp.isEmpty(objects)) { return; } prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getDescriptor(), query.getSession()); AbstractRecord keyRow = buildKeyRowForTargetUpdate(query); // Extract target field and its value. Construct insert // statement and execute it ClassDescriptor referenceDesc = getReferenceDescriptor(); AbstractSession session = query.getSession(); int objectIndex = 0; for (Object iter = cp.iteratorFor(objects); cp.hasNext(iter);) { AbstractRecord row = new DatabaseRecord(); row.mergeFrom(keyRow); Object wrappedObject = cp.nextEntry(iter, query.getSession()); Object object = cp.unwrapIteratorResult(wrappedObject); AbstractRecord databaseRow = referenceDesc.getObjectBuilder().buildRow(row, object, session, WriteType.INSERT); ContainerPolicy.copyMapDataToRow(cp.getKeyMappingDataForWriteQuery(wrappedObject, session), databaseRow); if (listOrderField != null) { databaseRow.put(listOrderField, objectIndex++); } InsertObjectQuery insertQuery = getInsertObjectQuery(session, referenceDesc); insertQuery.setObject(object); insertQuery.setCascadePolicy(query.getCascadePolicy()); insertQuery.setTranslationRow(databaseRow); insertQuery.setModifyRow(databaseRow); insertQuery.setIsPrepared(false); query.getSession().executeQuery(insertQuery); } } } } else { if (requiresDataModificationEvents() || containerPolicy.shouldUpdateForeignKeysPostInsert()) { updateTargetRowPostInsertSource(query); } } } } /** * INTERNAL: * Post-initialize source and target expression fields created when a mapping's selectionCriteria * is created early with only partly initialized fields. */ @Override public void postInitializeSourceAndTargetExpressions() { // EL Bug 426500 // postInitialize and set source expression fields using my descriptor if (this.sourceExpressionsToPostInitialize != null && !this.sourceExpressionsToPostInitialize.isEmpty()) { ClassDescriptor descriptor = getDescriptor(); ObjectBuilder objectBuilder = descriptor.getObjectBuilder(); for (Iterator expressions = this.sourceExpressionsToPostInitialize.iterator(); expressions.hasNext();) { Expression expression = expressions.next(); DatabaseField field = null; if (expression.isParameterExpression()) { field = ((ParameterExpression)expression).getField(); } else if (expression.isFieldExpression()) { field = ((FieldExpression)expression).getField(); } if (field != null && (field.getType() == null || field.getTypeName() == null)) { field.setType(objectBuilder.getFieldClassification(field)); } } } // postInitialize and set target expression fields using my reference descriptor if (this.targetExpressionsToPostInitialize != null && !this.targetExpressionsToPostInitialize.isEmpty()) { ClassDescriptor descriptor = getReferenceDescriptor(); ObjectBuilder objectBuilder = descriptor.getObjectBuilder(); for (Iterator expressions = this.targetExpressionsToPostInitialize.iterator(); expressions.hasNext();) { Expression expression = expressions.next(); DatabaseField field = null; if (expression.isParameterExpression()) { field = ((ParameterExpression)expression).getField(); } else if (expression.isFieldExpression()) { field = ((FieldExpression)expression).getField(); } if (field != null && (field.getType() == null || field.getTypeName() == null)) { field.setType(objectBuilder.getFieldClassification(field)); } } } } /** * INTERNAL: * Update the reference objects. */ @Override public void postUpdate(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { if (this.isReadOnly) { return; } if (!requiresDataModificationEvents() && !shouldObjectModifyCascadeToParts(query)){ return; } // if the target objects are not instantiated, they could not have been changed.... if (!isAttributeValueInstantiatedOrChanged(query.getObject())) { return; } if (query.getObjectChangeSet() != null) { // UnitOfWork writeChanges(query.getObjectChangeSet(), query); } else { // OLD COMMIT compareObjectsAndWrite(query); } } /** * INTERNAL: * Return the selection criteria used to IN batch fetching. */ @Override protected Expression buildBatchCriteria(ExpressionBuilder builder, ObjectLevelReadQuery query) { int size = this.targetForeignKeyFields.size(); if (size > 1) { // Support composite keys using nested IN. List fields = new ArrayList<>(size); for (DatabaseField targetForeignKeyField : this.targetForeignKeyFields) { fields.add(builder.getField(targetForeignKeyField)); } return query.getSession().getPlatform().buildBatchCriteriaForComplexId(builder, fields); } else { return query.getSession().getPlatform().buildBatchCriteria(builder, builder.getField(this.targetForeignKeyFields.get(0))); } } /** * INTERNAL: * Delete the reference objects. */ @Override public void preDelete(DeleteObjectQuery query) throws DatabaseException, OptimisticLockException { if (!shouldObjectModifyCascadeToParts(query)) { if (this.listOrderField != null) { updateTargetRowPreDeleteSource(query); } return; } AbstractSession session = query.getSession(); // If privately-owned parts have their privately-owned sub-parts, delete them one by one; // else delete everything in one shot. if (mustDeleteReferenceObjectsOneByOne()) { Object objects = getRealCollectionAttributeValueFromObject(query.getObject(), session); ContainerPolicy cp = getContainerPolicy(); if (this.isCascadeOnDeleteSetOnDatabase && session.isUnitOfWork()) { for (Object iterator = cp.iteratorFor(objects); cp.hasNext(iterator);) { Object wrappedObject = cp.nextEntry(iterator, session); Object object = cp.unwrapIteratorResult(wrappedObject); ((UnitOfWorkImpl)session).getCascadeDeleteObjects().add(object); } } int cascade = query.getCascadePolicy(); for (Object iterator = cp.iteratorFor(objects); cp.hasNext(iterator);) { Object wrappedObject = cp.nextEntry(iterator, session); Object object = cp.unwrapIteratorResult(wrappedObject); // PERF: Avoid query execution if already deleted. if (!session.getCommitManager().isCommitCompletedInPostOrIgnore(object) || this.containerPolicy.propagatesEventsToCollection()) { if (session.isUnitOfWork() && ((UnitOfWorkImpl)session).isObjectNew(object) ){ session.getCommitManager().markIgnoreCommit(object); } else { DeleteObjectQuery deleteQuery = new DeleteObjectQuery(); deleteQuery.setIsExecutionClone(true); deleteQuery.setObject(object); deleteQuery.setCascadePolicy(cascade); session.executeQuery(deleteQuery); this.containerPolicy.propogatePreDelete(deleteQuery, wrappedObject); } } } if (!session.isUnitOfWork()) { // This deletes any objects on the database, as the collection in memory may have been changed. // This is not required for unit of work, as the update would have already deleted these objects, // and the backup copy will include the same objects causing double deletes. deleteReferenceObjectsLeftOnDatabase(query); } } else { deleteAll(query, session); } } /** * Prepare a cascade locking policy. */ @Override public void prepareCascadeLockingPolicy() { CascadeLockingPolicy policy = new CascadeLockingPolicy(getDescriptor(), getReferenceDescriptor()); policy.setQueryKeyFields(getSourceKeysToTargetForeignKeys()); getReferenceDescriptor().addCascadeLockingPolicy(policy); } /** * INTERNAL: * Returns whether this mapping uses data modification events to complete its writes * @see UnidirectionalOneToManyMapping */ public boolean requiresDataModificationEvents(){ return this.listOrderField != null; } /** * PUBLIC: * The default add target query for mapping can be overridden by specifying the new query. * This query must set new value to target foreign key. */ public void setCustomAddTargetQuery(DataModifyQuery query) { addTargetQuery = query; hasCustomAddTargetQuery = true; } /** * PUBLIC: */ public void setAddTargetSQLString(String sqlString) { DataModifyQuery query = new DataModifyQuery(); query.setSQLString(sqlString); setCustomAddTargetQuery(query); } /** * PUBLIC: * The default remove target query for mapping can be overridden by specifying the new query. * In case target foreign key references the source, this query must set target foreign key to null. */ public void setCustomRemoveTargetQuery(DataModifyQuery query) { removeTargetQuery = query; hasCustomRemoveTargetQuery = true; } /** * PUBLIC: * The default remove all targets query for mapping can be overridden by specifying the new query. * This query must set all target foreign keys that reference the source to null. */ public void setCustomRemoveAllTargetsQuery(DataModifyQuery query) { removeAllTargetsQuery = query; hasCustomRemoveAllTargetsQuery = true; } /** * PUBLIC: * Set the SQL string used by the mapping to delete the target objects. * This allows the developer to override the SQL * generated by TopLink with a custom SQL statement or procedure call. * The arguments are * translated from the fields of the source row, by replacing the field names * marked by '#' with the values for those fields at execution time. * A one-to-many mapping will only use this delete all optimization if the target objects * can be deleted in a single SQL call. This is possible when the target objects * are in a single table, do not using locking, do not contain other privately-owned * parts, do not read subclasses, etc. *

* Example: "delete from PHONE where OWNER_ID = #EMPLOYEE_ID" */ @Override public void setDeleteAllSQLString(String sqlString) { DeleteAllQuery query = new DeleteAllQuery(); query.setSQLString(sqlString); setCustomDeleteAllQuery(query); } /** * PUBLIC: * Set the name of the session to execute the mapping's queries under. * This can be used by the session broker to override the default session * to be used for the target class. */ @Override public void setSessionName(String name) { super.setSessionName(name); if (addTargetQuery != null){ addTargetQuery.setSessionName(name); } removeTargetQuery.setSessionName(name); removeAllTargetsQuery.setSessionName(name); } /** * INTERNAL: * Set the source key field names associated with the mapping. * These must be in-order with the targetForeignKeyFieldNames. */ public void setSourceKeyFieldNames(List fieldNames) { List fields = new ArrayList<>(fieldNames.size()); for (Iterator iterator = fieldNames.iterator(); iterator.hasNext();) { fields.add(new DatabaseField(iterator.next())); } setSourceKeyFields(fields); } /** * INTERNAL: * Set the source key fields. */ public void setSourceKeyFields(List sourceKeyFields) { this.sourceKeyFields = sourceKeyFields; } /** * PUBLIC: * Define the target foreign key relationship in the one-to-many mapping. * This method can be used when the foreign and primary keys * have only a single field each. * (Use #addTargetForeignKeyFieldName(String, String) * for "composite" keys.) * Only the target foreign key field name is specified and the source * (primary) key field is * assumed to be the primary key of the source object. * Because the target object's table must store a foreign key to the source table, * the target object must map that foreign key, this is normally done through a * one-to-one mapping back-reference. Other options include: *

    *
  • use a DirectToFieldMapping and maintain the * foreign key fields directly in the target *
  • use a ManyToManyMapping *
  • use an AggregateCollectionMapping *
* @see DirectToFieldMapping * @see ManyToManyMapping * @see AggregateCollectionMapping */ public void setTargetForeignKeyFieldName(String targetForeignKeyFieldName) { getTargetForeignKeyFields().add(new DatabaseField(targetForeignKeyFieldName)); } /** * PUBLIC: * Define the target foreign key relationship in the one-to-many mapping. * This method is used for composite target foreign key relationships. * That is, the target object's table has multiple foreign key fields to * the source object's (typically primary) key fields. * Both the target foreign key field names and the corresponding source primary * key field names must be specified. */ public void setTargetForeignKeyFieldNames(String[] targetForeignKeyFieldNames, String[] sourceKeyFieldNames) { if (targetForeignKeyFieldNames.length != sourceKeyFieldNames.length) { throw DescriptorException.targetForeignKeysSizeMismatch(this); } for (int i = 0; i < targetForeignKeyFieldNames.length; i++) { addTargetForeignKeyFieldName(targetForeignKeyFieldNames[i], sourceKeyFieldNames[i]); } } /** * INTERNAL: * Set the target key field names associated with the mapping. * These must be in-order with the sourceKeyFieldNames. */ public void setTargetForeignKeyFieldNames(List fieldNames) { List fields = new ArrayList<>(fieldNames.size()); for (Iterator iterator = fieldNames.iterator(); iterator.hasNext();) { fields.add(new DatabaseField(iterator.next())); } setTargetForeignKeyFields(fields); } /** * INTERNAL: * Set the target fields. */ public void setTargetForeignKeyFields(List targetForeignKeyFields) { this.targetForeignKeyFields = targetForeignKeyFields; } /** * INTERNAL: * Set the target fields. */ protected void setTargetForeignKeysToSourceKeys(Map targetForeignKeysToSourceKeys) { this.targetForeignKeysToSourceKeys = targetForeignKeysToSourceKeys; } /** * Return whether any process leading to object modification * should also affect its parts. * Used by write, insert, update, and delete. */ @Override protected boolean shouldObjectModifyCascadeToParts(ObjectLevelModifyQuery query) { if (isReadOnly()) { return false; } if (isPrivateOwned()) { return true; } if (containerPolicy.isMappedKeyMapPolicy() && containerPolicy.requiresDataModificationEvents()){ return true; } return query.shouldCascadeAllParts(); } /** * INTERNAL * If it's not a map then target foreign key has been already modified (set to null). */ protected boolean shouldRemoveTargetQueryModifyTargetForeignKey() { return containerPolicy.isMapPolicy(); } /** * INTERNAL * Return true if this mapping supports cascaded version optimistic locking. */ @Override public boolean isCascadedLockingSupported() { return true; } /** * INTERNAL: * Return if this mapping support joining. */ @Override public boolean isJoiningSupported() { return true; } /** * INTERNAL: * Update target foreign keys after a new source was inserted. This follows following steps. */ public void updateTargetRowPostInsertSource(WriteObjectQuery query) throws DatabaseException { if (isReadOnly() || addTargetQuery == null) { return; } ContainerPolicy cp = getContainerPolicy(); Object objects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession()); if (cp.isEmpty(objects)) { return; } prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getDescriptor(), query.getSession()); AbstractRecord keyRow = buildKeyRowForTargetUpdate(query); // Extract target field and its value. Construct insert statement and execute it int size = targetPrimaryKeyFields.size(); int objectIndex = 0; for (Object iter = cp.iteratorFor(objects); cp.hasNext(iter);) { AbstractRecord databaseRow = new DatabaseRecord(); databaseRow.mergeFrom(keyRow); Object wrappedObject = cp.nextEntry(iter, query.getSession()); Object object = cp.unwrapIteratorResult(wrappedObject); for(int index = 0; index < size; index++) { DatabaseField targetPrimaryKey = targetPrimaryKeyFields.get(index); Object targetKeyValue = getReferenceDescriptor().getObjectBuilder().extractValueFromObjectForField(object, targetPrimaryKey, query.getSession()); databaseRow.put(targetPrimaryKey, targetKeyValue); } ContainerPolicy.copyMapDataToRow(cp.getKeyMappingDataForWriteQuery(wrappedObject, query.getSession()), databaseRow); if(listOrderField != null) { databaseRow.put(listOrderField, objectIndex++); } query.getSession().executeQuery(addTargetQuery, databaseRow); } } protected AbstractRecord buildKeyRowForTargetUpdate(ObjectLevelModifyQuery query){ return new DatabaseRecord(); } /** * INTERNAL: * Update target foreign key after a target object was added to the source. This follows following steps. *

- Extract primary key and its value from the source object. *

- Extract target key and its value from the target object. *

- Construct an update statement with above fields and values for target table. *

- execute the statement. */ public void updateTargetForeignKeyPostUpdateSource_ObjectAdded(ObjectLevelModifyQuery query, Object objectAdded, Map extraData) throws DatabaseException { if (isReadOnly() || addTargetQuery == null) { return; } ContainerPolicy cp = getContainerPolicy(); prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getDescriptor(), query.getSession()); AbstractRecord databaseRow = buildKeyRowForTargetUpdate(query); // Extract target field and its value. Construct insert statement and execute it int size = targetPrimaryKeyFields.size(); for (int index = 0; index < size; index++) { DatabaseField targetPrimaryKey = targetPrimaryKeyFields.get(index); Object targetKeyValue = getReferenceDescriptor().getObjectBuilder().extractValueFromObjectForField(cp.unwrapIteratorResult(objectAdded), targetPrimaryKey, query.getSession()); databaseRow.put(targetPrimaryKey, targetKeyValue); } ContainerPolicy.copyMapDataToRow(cp.getKeyMappingDataForWriteQuery(objectAdded, query.getSession()), databaseRow); if(listOrderField != null && extraData != null) { databaseRow.put(listOrderField, extraData.get(listOrderField)); } query.getSession().executeQuery(addTargetQuery, databaseRow); } /** * INTERNAL: * Update target foreign key after a target object was removed from the source. This follows following steps. *

- Extract primary key and its value from the source object. *

- Extract target key and its value from the target object. *

- Construct an update statement with above fields and values for target table. *

- execute the statement. */ public void updateTargetForeignKeyPostUpdateSource_ObjectRemoved(ObjectLevelModifyQuery query, Object objectRemoved) throws DatabaseException { if (this.isReadOnly) { return; } AbstractSession session = query.getSession(); prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getDescriptor(), session); AbstractRecord translationRow = new DatabaseRecord(); // Extract primary key and value from the source (use translation row). int size = this.sourceKeyFields.size(); AbstractRecord modifyRow = new DatabaseRecord(size); for (int index = 0; index < size; index++) { DatabaseField sourceKey = this.sourceKeyFields.get(index); DatabaseField targetForeignKey = this.targetForeignKeyFields.get(index); Object sourceKeyValue = query.getTranslationRow().get(sourceKey); translationRow.add(targetForeignKey, sourceKeyValue); // Need to set this value to null in the modify row. modifyRow.add(targetForeignKey, null); } if(listOrderField != null) { modifyRow.add(listOrderField, null); } ContainerPolicy cp = getContainerPolicy(); // Extract target field and its value from the object. size = targetPrimaryKeyFields.size(); for (int index = 0; index < size; index++) { DatabaseField targetPrimaryKey = targetPrimaryKeyFields.get(index); Object targetKeyValue = getReferenceDescriptor().getObjectBuilder().extractValueFromObjectForField(cp.unwrapIteratorResult(objectRemoved), targetPrimaryKey, session); translationRow.add(targetPrimaryKey, targetKeyValue); } // Need a different modify row than translation row, as the same field has different values in each. DataModifyQuery removeQuery = (DataModifyQuery)this.removeTargetQuery.clone(); removeQuery.setModifyRow(modifyRow); removeQuery.setHasModifyRow(true); removeQuery.setIsExecutionClone(true); session.executeQuery(removeQuery, translationRow); } /** * INTERNAL: * Update target foreign key after a target object was removed from the source. This follows following steps. *

- Extract primary key and its value from the source object. *

- Extract target key and its value from the target object. *

- Construct an update statement with above fields and values for target table. *

- execute the statement. */ public void updateTargetRowPreDeleteSource(ObjectLevelModifyQuery query) throws DatabaseException { if (this.isReadOnly) { return; } // Extract primary key and value from the source. int size = this.sourceKeyFields.size(); AbstractRecord translationRow = new DatabaseRecord(size); AbstractRecord modifyRow = new DatabaseRecord(size); for (int index = 0; index < size; index++) { DatabaseField sourceKey = this.sourceKeyFields.get(index); DatabaseField targetForeignKey = this.targetForeignKeyFields.get(index); Object sourceKeyValue = query.getTranslationRow().get(sourceKey); translationRow.add(targetForeignKey, sourceKeyValue); // Need to set this value to null in the modify row. modifyRow.add(targetForeignKey, null); } if(listOrderField != null) { modifyRow.add(listOrderField, null); } // Need a different modify row than translation row, as the same field has different values in each. DataModifyQuery removeQuery = (DataModifyQuery)this.removeAllTargetsQuery.clone(); removeQuery.setModifyRow(modifyRow); removeQuery.setHasModifyRow(true); removeQuery.setIsExecutionClone(true); query.getSession().executeQuery(removeQuery, translationRow); } /** * INTERNAL: * Used to verify whether the specified object is deleted or not. */ @Override public boolean verifyDelete(Object object, AbstractSession session) throws DatabaseException { if (this.isPrivateOwned() || isCascadeRemove()) { Object objects = getRealCollectionAttributeValueFromObject(object, session); ContainerPolicy containerPolicy = getContainerPolicy(); for (Object iter = containerPolicy.iteratorFor(objects); containerPolicy.hasNext(iter);) { if (!session.verifyDelete(containerPolicy.next(iter, session))) { return false; } } } return true; } public boolean shouldDeferInsert() { if (this.shouldDeferInserts == null) { this.shouldDeferInserts = true; } return this.shouldDeferInserts; } public void setShouldDeferInsert(boolean defer) { this.shouldDeferInserts = defer; } /** * INTERNAL: * Returns a clone of InsertObjectQuery from the ClassDescriptor's DescriptorQueryManager or a new one */ protected InsertObjectQuery getInsertObjectQuery(AbstractSession session, ClassDescriptor desc) { InsertObjectQuery insertQuery = desc.getQueryManager().getInsertQuery(); if (insertQuery == null) { insertQuery = new InsertObjectQuery(); insertQuery.setDescriptor(desc); insertQuery.checkPrepare(session, insertQuery.getTranslationRow()); } else { // Ensure the query has been prepared. insertQuery.checkPrepare(session, insertQuery.getTranslationRow()); insertQuery = (InsertObjectQuery)insertQuery.clone(); } insertQuery.setIsExecutionClone(true); return insertQuery; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy