| /* |
| * 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.eis.mappings; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Vector; |
| |
| import org.eclipse.persistence.eis.EISDescriptor; |
| import org.eclipse.persistence.eis.EISException; |
| import org.eclipse.persistence.exceptions.DatabaseException; |
| import org.eclipse.persistence.exceptions.DescriptorException; |
| import org.eclipse.persistence.exceptions.OptimisticLockException; |
| import org.eclipse.persistence.exceptions.QueryException; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.expressions.ExpressionBuilder; |
| import org.eclipse.persistence.indirection.ValueHolder; |
| import org.eclipse.persistence.internal.descriptors.ObjectBuilder; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.helper.DatabaseTable; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.indirection.EISOneToManyQueryBasedValueHolder; |
| import org.eclipse.persistence.internal.oxm.XPathEngine; |
| import org.eclipse.persistence.internal.queries.ContainerPolicy; |
| import org.eclipse.persistence.internal.queries.JoinedAttributeManager; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.ChangeRecord; |
| import org.eclipse.persistence.internal.sessions.MergeManager; |
| import org.eclipse.persistence.internal.sessions.ObjectChangeSet; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.mappings.CollectionMapping; |
| import org.eclipse.persistence.oxm.XMLField; |
| import org.eclipse.persistence.oxm.record.DOMRecord; |
| import org.eclipse.persistence.oxm.record.XMLRecord; |
| import org.eclipse.persistence.queries.Call; |
| 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.QueryByExamplePolicy; |
| import org.eclipse.persistence.queries.ReadQuery; |
| import org.eclipse.persistence.queries.WriteObjectQuery; |
| import org.w3c.dom.Element; |
| |
| /** |
| * <p>An EIS one-to-many mapping is a reference mapping that represents the relationship between |
| * a single source object and a collection of mapped persistent Java objects. The source object usually |
| * contains a foreign key (pointer) to the target objects (key on source); alternatively, the target |
| * objects may contain a foreign key to the source object (key on target). Because both the source |
| * and target objects use interactions, they must all be configured as root object types. |
| * |
| * <table border="1"> |
| * <caption>Record formats</caption> |
| * <tr> |
| * <th id="c1">Record Type</th> |
| * <th id="c2">Description</th> |
| * </tr> |
| * <tr> |
| * <td headers="c1">Indexed</td> |
| * <td headers="c2">Ordered collection of record elements. The indexed record EIS format |
| * enables Java class attribute values to be retreived by position or index.</td> |
| * </tr> |
| * <tr> |
| * <td headers="c1">Mapped</td> |
| * <td headers="c2">Key-value map based representation of record elements. The mapped record |
| * EIS format enables Java class attribute values to be retreived by an object key.</td> |
| * </tr> |
| * <tr> |
| * <td headers="c1">XML</td> |
| * <td headers="c2">Record/Map representation of an XML DOM element.</td> |
| * </tr> |
| * </table> |
| * |
| * @see org.eclipse.persistence.eis.EISDescriptor#useIndexedRecordFormat |
| * @see org.eclipse.persistence.eis.EISDescriptor#useMappedRecordFormat |
| * @see org.eclipse.persistence.eis.EISDescriptor#useXMLRecordFormat |
| * |
| * @since Oracle TopLink 10<i>g</i> Release 2 (10.1.3) |
| */ |
| public class EISOneToManyMapping extends CollectionMapping implements EISMapping { |
| |
| /** Keeps track if any of the fields are foreign keys. */ |
| protected boolean isForeignKeyRelationship; |
| |
| /** The target foreign key fields that reference the sourceKeyFields. */ |
| protected transient List<DatabaseField> targetForeignKeyFields; |
| |
| /** The (typically primary) source key fields that are referenced by the targetForeignKeyFields. */ |
| protected transient List<DatabaseField> sourceForeignKeyFields; |
| |
| /** This maps the source foreign key fields to the corresponding (primary) target key fields. */ |
| protected transient Map<DatabaseField, DatabaseField> sourceForeignKeysToTargetKeys; |
| |
| /** The grouping-element field. */ |
| protected DatabaseField foreignKeyGroupingElement; |
| |
| public EISOneToManyMapping() { |
| this.isForeignKeyRelationship = false; |
| this.sourceForeignKeyFields = new ArrayList<>(1); |
| this.targetForeignKeyFields = new ArrayList<>(1); |
| this.sourceForeignKeysToTargetKeys = new HashMap<>(2); |
| this.deleteAllQuery = new DeleteAllQuery(); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isEISMapping() { |
| return true; |
| } |
| |
| /** |
| * PUBLIC: |
| * Define the source foreign key relationship in the one-to-many mapping. |
| * This method is used for composite source foreign key relationships. |
| * That is, the source object's table has multiple foreign key fields |
| * that are references to |
| * the target object's (typically primary) key fields. |
| * Both the source foreign key field name and the corresponding |
| * target primary key field name must be specified. |
| */ |
| @Override |
| public void addForeignKeyField(DatabaseField sourceForeignKeyField, DatabaseField targetKeyField) { |
| this.getSourceForeignKeyFields().add(sourceForeignKeyField); |
| this.getTargetForeignKeyFields().add(targetKeyField); |
| |
| this.setIsForeignKeyRelationship(true); |
| } |
| |
| /** |
| * PUBLIC: |
| * Define the source foreign key relationship in the one-to-many mapping. |
| * This method is used for composite source foreign key relationships. |
| * That is, the source object's table has multiple foreign key fields |
| * that are references to |
| * the target object's (typically primary) key fields. |
| * Both the source foreign key field name and the corresponding |
| * target primary key field name must be specified. |
| */ |
| public void addForeignKeyFieldName(String sourceForeignKeyFieldName, String targetKeyFieldName) { |
| this.addForeignKeyField(new DatabaseField(sourceForeignKeyFieldName), new DatabaseField(targetKeyFieldName)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the 1-M mapping has a foreign key dependency to its target. |
| * This is true if any of the foreign key fields are true foreign keys, |
| * i.e. populated on write from the targets primary key. |
| */ |
| public boolean isForeignKeyRelationship() { |
| return isForeignKeyRelationship; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set if the 1-M mapping has a foreign key dependency to its target. |
| * This is true if any of the foreign key fields are true foreign keys, |
| * i.e. populated on write from the targets primary key. |
| */ |
| public void setIsForeignKeyRelationship(boolean isForeignKeyRelationship) { |
| this.isForeignKeyRelationship = isForeignKeyRelationship; |
| } |
| |
| /** |
| * Get the grouping element field on the mapping. |
| * This is an optional setting. |
| */ |
| public DatabaseField getForeignKeyGroupingElement() { |
| return this.foreignKeyGroupingElement; |
| } |
| |
| /** |
| * Set the grouping element field on the mapping. |
| * This is an optional setting; however it is a required setting when |
| * there are more than one foreign keys specified |
| */ |
| public void setForeignKeyGroupingElement(String name) { |
| setForeignKeyGroupingElement(new DatabaseField(name)); |
| } |
| |
| @Override |
| public boolean hasCustomDeleteAllQuery() { |
| return hasCustomDeleteAllQuery; |
| } |
| |
| @Override |
| public ModifyQuery getDeleteAllQuery() { |
| if (deleteAllQuery == null) { |
| deleteAllQuery = new DataModifyQuery(); |
| } |
| return deleteAllQuery; |
| } |
| |
| /** |
| * PUBLIC: |
| * The default delete all call for this mapping can be overridden by specifying the new call. |
| * This call is responsible for doing the deletion required by the mapping, |
| * such as optimized delete all of target objects for 1-M. |
| */ |
| @Override |
| public void setDeleteAllCall(Call call) { |
| DeleteAllQuery deleteAllQuery = new DeleteAllQuery(); |
| deleteAllQuery.setCall(call); |
| setDeleteAllQuery(deleteAllQuery); |
| setHasCustomDeleteAllQuery(true); |
| } |
| |
| /** |
| * Set if the grouping element field on the mapping. |
| * This is an optional setting; however it is a required setting when |
| * there are more than one foreign keys specified. |
| */ |
| public void setForeignKeyGroupingElement(DatabaseField field) { |
| this.foreignKeyGroupingElement = field; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the source foreign key fields. |
| */ |
| public List<DatabaseField> getSourceForeignKeyFields() { |
| return sourceForeignKeyFields; |
| } |
| |
| /** |
| * INTERNAL: |
| * Sets the source foreign key fields. |
| */ |
| public void setSourceForeignKeyFields(List<DatabaseField> fields) { |
| sourceForeignKeyFields = fields; |
| if ((fields != null) && (fields.size() > 0)) { |
| this.setIsForeignKeyRelationship(true); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the source foreign key fields. |
| */ |
| public List<DatabaseField> getTargetForeignKeyFields() { |
| return targetForeignKeyFields; |
| } |
| |
| /** |
| * INTERNAL: |
| * Sets the target foreign key fields. |
| */ |
| public void setTargetForeignKeyFields(List<DatabaseField> fields) { |
| targetForeignKeyFields = fields; |
| } |
| |
| /** |
| * INTERNAL: |
| * Sets the target foreign key fields. |
| */ |
| public Map<DatabaseField, DatabaseField> getSourceForeignKeysToTargetKeys() { |
| return sourceForeignKeysToTargetKeys; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the source keys to target keys fields association. |
| */ |
| public void setSourceForeignKeysToTargetKeys(Map<DatabaseField, DatabaseField> sourceToTargetKeyFields) { |
| this.sourceForeignKeysToTargetKeys = sourceToTargetKeyFields; |
| if ((sourceToTargetKeyFields != null) && (sourceToTargetKeyFields.keySet().size() > 0)) { |
| this.setIsForeignKeyRelationship(true); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether the mapping has any inverse constraint dependencies, |
| * such as foreign keys. |
| */ |
| @Override |
| public boolean hasInverseConstraintDependency() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Initialize the mapping. |
| */ |
| @Override |
| public void initialize(AbstractSession session) throws DescriptorException { |
| super.initialize(session); |
| |
| if ((this.getForeignKeyGroupingElement() == null) && (this.getSourceForeignKeysToTargetKeys().size() > 1)) { |
| throw EISException.groupingElementRequired(); |
| } |
| |
| if (this.getForeignKeyGroupingElement() != null) { |
| DatabaseField field = this.getDescriptor().buildField(this.getForeignKeyGroupingElement()); |
| setForeignKeyGroupingElement(field); |
| } |
| |
| this.initializeSourceForeignKeysToTargetKeys(); |
| |
| if (shouldInitializeSelectionCriteria()) { |
| initializeSelectionCriteria(session); |
| } |
| |
| this.initializeDeleteAllQuery(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Selection criteria is created with source foreign keys and target keys. |
| * This criteria is then used to read target records from the table. |
| * |
| * CR#3922 - This method is almost the same as buildSelectionCriteria() the difference |
| * is that getSelectionCriteria() is called |
| */ |
| protected void initializeSelectionCriteria(AbstractSession session) { |
| if (this.getSourceForeignKeysToTargetKeys().isEmpty()) { |
| throw DescriptorException.noForeignKeysAreSpecified(this); |
| } |
| |
| Expression criteria; |
| Expression builder = new ExpressionBuilder(); |
| Iterator<DatabaseField> keyIterator = getSourceForeignKeysToTargetKeys().keySet().iterator(); |
| while (keyIterator.hasNext()) { |
| DatabaseField foreignKey = keyIterator.next(); |
| DatabaseField targetKey = getSourceForeignKeysToTargetKeys().get(foreignKey); |
| |
| Expression expression = builder.getField(targetKey).equal(builder.getParameter(foreignKey)); |
| criteria = expression.and(getSelectionCriteria()); |
| setSelectionCriteria(criteria); |
| } |
| } |
| |
| protected void initializeSourceForeignKeysToTargetKeys() throws DescriptorException { |
| // Since we require a custom selection query, these keys are optional. |
| if (getSourceForeignKeyFields().size() != getTargetForeignKeyFields().size()) { |
| throw DescriptorException.sizeMismatchOfForeignKeys(this); |
| } |
| |
| for (int i = 0; i < getTargetForeignKeyFields().size(); i++) { |
| DatabaseField field = getReferenceDescriptor().buildField(getTargetForeignKeyFields().get(i)); |
| getTargetForeignKeyFields().set(i, field); |
| } |
| |
| for (int i = 0; i < getSourceForeignKeyFields().size(); i++) { |
| DatabaseField field = getDescriptor().buildField(getSourceForeignKeyFields().get(i)); |
| getSourceForeignKeyFields().set(i, field); |
| getSourceForeignKeysToTargetKeys().put(field, getTargetForeignKeyFields().get(i)); |
| } |
| } |
| |
| /** |
| * Initialize the delete all query. |
| * This query is used to delete the collection of objects from the |
| * database. |
| */ |
| protected void initializeDeleteAllQuery() { |
| ((DeleteAllQuery)this.getDeleteAllQuery()).setReferenceClass(this.getReferenceClass()); |
| if (!this.hasCustomDeleteAllQuery()) { |
| // the selection criteria are re-used by the delete all query |
| this.getDeleteAllQuery().setSelectionCriteria(this.getSelectionCriteria()); |
| } |
| } |
| |
| /** |
| * Fix field names for XML data descriptors. |
| * Since fields are fixed to use text() by default in descriptor, ensure the correct non text field is used here. |
| */ |
| @Override |
| public void preInitialize(AbstractSession session) { |
| super.preInitialize(session); |
| if (((EISDescriptor)this.descriptor).isXMLFormat()) { |
| if ((this.foreignKeyGroupingElement != null) && !(this.foreignKeyGroupingElement instanceof XMLField)) { |
| XMLField newField = new XMLField(this.foreignKeyGroupingElement.getName()); |
| this.foreignKeyGroupingElement = newField; |
| } |
| } |
| } |
| |
| /** |
| * 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 (isForeignKeyRelationship()) { |
| return super.shouldObjectModifyCascadeToParts(query); |
| } else { |
| if (this.isReadOnly()) { |
| return false; |
| } |
| |
| if (this.isPrivateOwned()) { |
| return true; |
| } |
| |
| return query.shouldCascadeAllParts(); |
| } |
| } |
| |
| /** |
| * 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()) { |
| Object objects = this.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; |
| |
| } |
| |
| /** |
| * INTERNAL: |
| * Insert the reference objects. |
| */ |
| @Override |
| public void postInsert(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { |
| if (isForeignKeyRelationship()) { |
| return; |
| } |
| |
| if (!this.shouldObjectModifyCascadeToParts(query)) { |
| return; |
| } |
| |
| // only cascade dependents in UOW |
| if (query.shouldCascadeOnlyDependentParts()) { |
| return; |
| } |
| |
| Object objects = this.getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession()); |
| |
| // insert each object one by one |
| ContainerPolicy cp = this.getContainerPolicy(); |
| for (Object iter = cp.iteratorFor(objects); cp.hasNext(iter);) { |
| Object object = cp.next(iter, query.getSession()); |
| if (this.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 or 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); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Update the reference objects. |
| */ |
| @Override |
| public void postUpdate(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { |
| if (isForeignKeyRelationship()) { |
| return; |
| } |
| |
| if (!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: |
| * Delete the reference objects. |
| */ |
| @Override |
| public void postDelete(DeleteObjectQuery query) throws DatabaseException, OptimisticLockException { |
| if (!isForeignKeyRelationship()) { |
| return; |
| } |
| |
| if (!this.shouldObjectModifyCascadeToParts(query)) { |
| return; |
| } |
| Object referenceObjects = this.getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession()); |
| |
| // if we have a custom delete all query, use it; |
| // otherwise, delete the reference objects one by one |
| if (this.hasCustomDeleteAllQuery()) { |
| this.deleteAll(query, referenceObjects); |
| } else { |
| ContainerPolicy cp = this.getContainerPolicy(); |
| for (Object iter = cp.iteratorFor(referenceObjects); cp.hasNext(iter);) { |
| DeleteObjectQuery deleteQuery = new DeleteObjectQuery(); |
| deleteQuery.setIsExecutionClone(true); |
| deleteQuery.setObject(cp.next(iter, query.getSession())); |
| deleteQuery.setCascadePolicy(query.getCascadePolicy()); |
| query.getSession().executeQuery(deleteQuery); |
| } |
| if (!query.getSession().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. |
| this.deleteReferenceObjectsLeftOnDatabase(query); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Delete the reference objects. |
| */ |
| @Override |
| public void preDelete(DeleteObjectQuery query) throws DatabaseException, OptimisticLockException { |
| if (isForeignKeyRelationship()) { |
| return; |
| } |
| |
| if (!this.shouldObjectModifyCascadeToParts(query)) { |
| return; |
| } |
| |
| Object objects = this.getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession()); |
| ContainerPolicy cp = this.getContainerPolicy(); |
| |
| // if privately-owned parts have their privately-owned sub-parts, delete them one by one; |
| // else delete everything in one shot |
| if (this.mustDeleteReferenceObjectsOneByOne()) { |
| for (Object iter = cp.iteratorFor(objects); cp.hasNext(iter);) { |
| DeleteObjectQuery deleteQuery = new DeleteObjectQuery(); |
| deleteQuery.setIsExecutionClone(true); |
| deleteQuery.setObject(cp.next(iter, query.getSession())); |
| deleteQuery.setCascadePolicy(query.getCascadePolicy()); |
| query.getSession().executeQuery(deleteQuery); |
| } |
| if (!query.getSession().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. |
| this.deleteReferenceObjectsLeftOnDatabase(query); |
| } |
| } else { |
| this.deleteAll(query); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Insert privately owned parts |
| */ |
| @Override |
| public void preInsert(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { |
| if (!this.isForeignKeyRelationship()) { |
| return; |
| } |
| |
| if (!this.shouldObjectModifyCascadeToParts(query)) { |
| return; |
| } |
| |
| // only cascade dependents in UOW |
| if (query.shouldCascadeOnlyDependentParts()) { |
| return; |
| } |
| |
| Object objects = this.getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession()); |
| |
| // insert each object one by one |
| ContainerPolicy cp = this.getContainerPolicy(); |
| for (Object iter = cp.iteratorFor(objects); cp.hasNext(iter);) { |
| Object object = cp.next(iter, query.getSession()); |
| if (this.isPrivateOwned()) { |
| // no need to set changeset here as insert is just a copy of the object anyway |
| InsertObjectQuery insertQuery = new InsertObjectQuery(); |
| insertQuery.setIsExecutionClone(true); |
| insertQuery.setObject(object); |
| insertQuery.setCascadePolicy(query.getCascadePolicy()); |
| query.getSession().executeQuery(insertQuery); |
| } else { |
| // This will happen in a unit of work or 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); |
| if (query.getSession().isUnitOfWork()) { |
| UnitOfWorkChangeSet uowChangeSet = (UnitOfWorkChangeSet)((UnitOfWorkImpl)query.getSession()).getUnitOfWorkChangeSet(); |
| if (uowChangeSet != null) { |
| writeQuery.setObjectChangeSet((ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(object)); |
| } |
| } |
| writeQuery.setObject(object); |
| writeQuery.setCascadePolicy(query.getCascadePolicy()); |
| query.getSession().executeQuery(writeQuery); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Update the privately owned parts. |
| */ |
| @Override |
| public void preUpdate(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { |
| if (!isForeignKeyRelationship()) { |
| return; |
| } |
| |
| if (!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: |
| * Build and return a new element based on the change set. |
| */ |
| public Object buildAddedElementFromChangeSet(Object changeSet, MergeManager mergeManager, AbstractSession targetSession) { |
| ObjectChangeSet objectChangeSet = (ObjectChangeSet)changeSet; |
| |
| if (this.shouldMergeCascadeParts(mergeManager)) { |
| Object targetElement = null; |
| if (mergeManager.shouldMergeChangesIntoDistributedCache()) { |
| targetElement = objectChangeSet.getTargetVersionOfSourceObject(mergeManager, mergeManager.getSession(), true); |
| } else { |
| targetElement = objectChangeSet.getUnitOfWorkClone(); |
| } |
| mergeManager.mergeChanges(targetElement, objectChangeSet, targetSession); |
| } |
| |
| return this.buildElementFromChangeSet(changeSet, mergeManager, targetSession); |
| } |
| |
| /** |
| * INTERNAL: |
| * Build and return a change set for the specified element. |
| */ |
| public Object buildChangeSet(Object element, ObjectChangeSet owner, AbstractSession session) { |
| ObjectBuilder objectBuilder = session.getDescriptor(element).getObjectBuilder(); |
| return objectBuilder.createObjectChangeSet(element, (UnitOfWorkChangeSet)owner.getUOWChangeSet(), session); |
| } |
| |
| /** |
| * Build and return a new element based on the change set. |
| */ |
| protected Object buildElementFromChangeSet(Object changeSet, MergeManager mergeManager, AbstractSession targetSession) { |
| return ((ObjectChangeSet)changeSet).getTargetVersionOfSourceObject(mergeManager, targetSession); |
| } |
| |
| /** |
| * INTERNAL: |
| * Build and return a new element based on the specified element. |
| */ |
| public Object buildElementFromElement(Object element, MergeManager mergeManager, AbstractSession targetSession) { |
| if (this.shouldMergeCascadeParts(mergeManager)) { |
| ObjectChangeSet objectChangeSet = null; |
| if (mergeManager.getSession().isUnitOfWork()) { |
| UnitOfWorkChangeSet uowChangeSet = (UnitOfWorkChangeSet)((UnitOfWorkImpl)mergeManager.getSession()).getUnitOfWorkChangeSet(); |
| if (uowChangeSet != null) { |
| objectChangeSet = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(element); |
| } |
| } |
| Object mergeElement = mergeManager.getObjectToMerge(element, referenceDescriptor, targetSession); |
| mergeManager.mergeChanges(mergeElement, objectChangeSet, targetSession); |
| } |
| |
| return mergeManager.getTargetVersionOfSourceObject(element, referenceDescriptor, targetSession); |
| |
| } |
| |
| /** |
| * INTERNAL: |
| * In case Query By Example is used, this method builds and returns an expression that |
| * corresponds to a single attribute and it's value. |
| */ |
| @Override |
| public Expression buildExpression(Object queryObject, QueryByExamplePolicy policy, Expression expressionBuilder, Map processedObjects, AbstractSession session) { |
| if (policy.shouldValidateExample()){ |
| throw QueryException.unsupportedMappingQueryByExample(queryObject.getClass().getName(), this); |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Build and return a new element based on the change set. |
| */ |
| public Object buildRemovedElementFromChangeSet(Object changeSet, MergeManager mergeManager, AbstractSession targetSession) { |
| ObjectChangeSet objectChangeSet = (ObjectChangeSet)changeSet; |
| |
| if (!mergeManager.shouldMergeChangesIntoDistributedCache()) { |
| mergeManager.registerRemovedNewObjectIfRequired(objectChangeSet.getUnitOfWorkClone()); |
| } |
| |
| return this.buildElementFromChangeSet(changeSet, mergeManager, targetSession); |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone the appropriate attributes. |
| */ |
| @Override |
| @SuppressWarnings({"unchecked"}) |
| public Object clone() { |
| EISOneToManyMapping clone = (EISOneToManyMapping)super.clone(); |
| clone.setSourceForeignKeysToTargetKeys((Map<DatabaseField, DatabaseField>)((HashMap<DatabaseField, DatabaseField>)getSourceForeignKeysToTargetKeys()).clone()); |
| return clone; |
| } |
| |
| /** |
| * Return all the fields mapped by the mapping. |
| */ |
| @Override |
| protected Vector<DatabaseField> collectFields() { |
| if (isForeignKeyRelationship()) { |
| if (this.getForeignKeyGroupingElement() != null) { |
| Vector<DatabaseField> fields = new Vector<>(1); |
| fields.addElement(this.getForeignKeyGroupingElement()); |
| return fields; |
| } else { |
| return NO_FIELDS; |
| } |
| } else { |
| return NO_FIELDS; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the non-null elements and return true if they are alike. |
| */ |
| public boolean compareElements(Object element1, Object element2, AbstractSession session) { |
| if (!isForeignKeyRelationship()) { |
| return false; |
| } |
| |
| Object primaryKey1 = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(element1, session); |
| Object primaryKey2 = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(element2, session); |
| |
| if (!primaryKey1.equals(primaryKey2)) { |
| return false; |
| } |
| |
| if (this.isPrivateOwned()) { |
| return session.compareObjects(element1, element2); |
| } else { |
| return true; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether the element's user-defined Map key has changed |
| * since it was cloned from the original version. |
| * Object elements can change their keys without detection. |
| * Get the original object and compare keys. |
| */ |
| public boolean mapKeyHasChanged(Object element, AbstractSession session) { |
| //CR 4172 compare keys will now get backup if required |
| return !this.getContainerPolicy().compareKeys(element, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the non-null elements and return true if they are alike. |
| * Here we use object identity. |
| */ |
| public boolean compareElementsForChange(Object element1, Object element2, AbstractSession session) { |
| return element1 == element2; |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the changes between two collections. Element comparisons are |
| * made using identity and, when appropriate, the value of the element's key |
| * for the Map container. |
| */ |
| @Override |
| public ChangeRecord compareForChange(Object clone, Object backup, ObjectChangeSet owner, AbstractSession session) { |
| if (isForeignKeyRelationship()) { |
| if ((this.getAttributeValueFromObject(clone) != null) && (!this.isAttributeValueInstantiatedOrChanged(clone))) { |
| return null;// never instantiated - no changes to report |
| } |
| return (new EISOneToManyMappingHelper(this)).compareForChange(clone, backup, owner, session); |
| } else { |
| return super.compareForChange(clone, backup, owner, session); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the attributes belonging to this mapping for the objects. |
| */ |
| @Override |
| public boolean compareObjects(Object object1, Object object2, AbstractSession session) { |
| if (isForeignKeyRelationship()) { |
| return (new EISOneToManyMappingHelper(this)).compareObjects(object1, object2, session); |
| } |
| return super.compareObjects(object1, object2, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * If the mapping has a foreign key, it is order, so must use a different merge. |
| */ |
| @Override |
| public void mergeChangesIntoObject(Object target, ChangeRecord chgRecord, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| if (isForeignKeyRelationship()) { |
| (new EISOneToManyMappingHelper(this)).mergeChangesIntoObject(target, chgRecord, source, mergeManager, targetSession); |
| return; |
| } |
| super.mergeChangesIntoObject(target, chgRecord, source, mergeManager, targetSession); |
| } |
| |
| /** |
| * INTERNAL: |
| * If the mapping has a foreign key, it is order, so must use a different merge. |
| */ |
| @Override |
| public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| if (isForeignKeyRelationship()) { |
| (new EISOneToManyMappingHelper(this)).mergeIntoObject(target, isTargetUnInitialized, source, mergeManager, targetSession); |
| return; |
| } |
| super.mergeIntoObject(target, isTargetUnInitialized, source, mergeManager, targetSession); |
| } |
| |
| /** |
| * ADVANCED: |
| * This method is used to have an object add to a collection once the changeSet is applied |
| * The referenceKey parameter should only be used for direct Maps. |
| */ |
| @Override |
| public void simpleAddToCollectionChangeRecord(Object referenceKey, Object changeSetToAdd, ObjectChangeSet changeSet, AbstractSession session) { |
| (new EISOneToManyMappingHelper(this)).simpleAddToCollectionChangeRecord(referenceKey, changeSetToAdd, changeSet, session); |
| } |
| |
| /** |
| * ADVANCED: |
| * This method is used to have an object removed from a collection once the changeSet is applied |
| * The referenceKey parameter should only be used for direct Maps. |
| */ |
| @Override |
| public void simpleRemoveFromCollectionChangeRecord(Object referenceKey, Object changeSetToRemove, ObjectChangeSet changeSet, AbstractSession session) { |
| (new EISOneToManyMappingHelper(this)).simpleRemoveFromCollectionChangeRecord(referenceKey, changeSetToRemove, changeSet, session); |
| } |
| |
| /** |
| * Delete all the reference objects. |
| */ |
| protected void deleteAll(DeleteObjectQuery query, Object referenceObjects) throws DatabaseException { |
| ((DeleteAllQuery)this.getDeleteAllQuery()).executeDeleteAll(query.getSession().getSessionForClass(this.getReferenceClass()), query.getTranslationRow(), this.getContainerPolicy().vectorFor(referenceObjects, query.getSession())); |
| } |
| |
| /** |
| * Delete all the reference objects. |
| */ |
| protected void deleteAll(DeleteObjectQuery query) throws DatabaseException { |
| Object referenceObjects = this.getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession()); |
| deleteAll(query, referenceObjects); |
| } |
| |
| /** |
| * 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 = this.readPrivateOwnedForObject(query); |
| |
| // delete all these objects one by one |
| ContainerPolicy cp = this.getContainerPolicy(); |
| for (Object iter = cp.iteratorFor(objects); cp.hasNext(iter);) { |
| query.getSession().deleteObject(cp.next(iter, query.getSession())); |
| } |
| } |
| |
| /** |
| * Build and return a database row that contains a foreign key for the specified reference |
| * object. This will be stored in the nested row(s). |
| */ |
| protected AbstractRecord extractKeyRowFromReferenceObject(Object object, AbstractSession session, AbstractRecord parentRecord) { |
| int size = this.sourceForeignKeyFields.size(); |
| AbstractRecord result; |
| if (((EISDescriptor) this.getDescriptor()).isXMLFormat()) { |
| Element newNode = XPathEngine.getInstance().createUnownedElement(((XMLRecord)parentRecord).getDOM(), (XMLField)getForeignKeyGroupingElement()); |
| result = new DOMRecord(newNode); |
| ((DOMRecord)result).setSession(session); |
| } else { |
| result = this.descriptor.getObjectBuilder().createRecord(size, session); |
| } |
| for (int index = 0; index < size; index++) { |
| DatabaseField fkField = this.sourceForeignKeyFields.get(index); |
| if (object == null) { |
| result.add(fkField, null); |
| } else { |
| DatabaseField pkField = this.sourceForeignKeysToTargetKeys.get(fkField); |
| Object value = this.referenceDescriptor.getObjectBuilder().extractValueFromObjectForField(object, pkField, session); |
| result.add(fkField, value); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the value of the reference attribute or a value holder. |
| * Check whether the mapping's attribute should be optimized through batch and joining. |
| */ |
| @Override |
| public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, CacheKey cacheKey, AbstractSession executionSession, boolean isTargetProtected, Boolean[] wasCacheUsed) throws DatabaseException { |
| if (this.descriptor.getCachePolicy().isProtectedIsolation()) { |
| if (this.isCacheable && isTargetProtected && cacheKey != null) { |
| //cachekey will be null when isolating to uow |
| //used cached collection |
| Object cached = cacheKey.getObject(); |
| if (cached != null) { |
| if (wasCacheUsed != null){ |
| wasCacheUsed[0] = Boolean.TRUE; |
| } |
| //this will just clone the indirection. |
| //the indirection object is responsible for cloning the value. |
| return getAttributeValueFromObject(cached); |
| } |
| } else if (!this.isCacheable && !isTargetProtected && cacheKey != null) { |
| return this.indirectionPolicy.buildIndirectObject(new ValueHolder<>(null)); |
| } |
| } |
| if (((EISDescriptor) this.getDescriptor()).isXMLFormat()) { |
| ((XMLRecord) row).setSession(executionSession); |
| } |
| |
| ReadQuery targetQuery = getSelectionQuery(); |
| if (!this.isForeignKeyRelationship) { |
| // if the source query is cascading then the target query must use the same settings |
| if (targetQuery.isObjectLevelReadQuery() && (sourceQuery.shouldCascadeAllParts() || (sourceQuery.shouldCascadePrivateParts() && isPrivateOwned()) || (sourceQuery.shouldCascadeByMapping() && this.cascadeRefresh))) { |
| targetQuery = (ObjectLevelReadQuery)targetQuery.clone(); |
| ((ObjectLevelReadQuery)targetQuery).setShouldRefreshIdentityMapResult(sourceQuery.shouldRefreshIdentityMapResult()); |
| targetQuery.setCascadePolicy(sourceQuery.getCascadePolicy()); |
| //CR #4365 |
| targetQuery.setQueryId(sourceQuery.getQueryId()); |
| // For queries that have turned caching off, such as aggregate collection, leave it off. |
| if (targetQuery.shouldMaintainCache()) { |
| targetQuery.setShouldMaintainCache(sourceQuery.shouldMaintainCache()); |
| } |
| } |
| |
| return getIndirectionPolicy().valueFromQuery(targetQuery, row, sourceQuery.getSession()); |
| } else { |
| if (getIndirectionPolicy().usesIndirection()) { |
| EISOneToManyQueryBasedValueHolder valueholder = new EISOneToManyQueryBasedValueHolder(this, targetQuery, row, sourceQuery.getSession()); |
| return getIndirectionPolicy().buildIndirectObject(valueholder); |
| } else { |
| Vector<AbstractRecord> subRows = getForeignKeyRows(row, executionSession); |
| |
| if (subRows == null) { |
| return null; |
| } |
| |
| ContainerPolicy cp = this.getContainerPolicy(); |
| Object results = cp.containerInstance(subRows.size()); |
| |
| for (int i = 0; i < subRows.size(); i++) { |
| XMLRecord subRow = (XMLRecord)subRows.elementAt(i); |
| subRow.setSession(executionSession); |
| Object object = getIndirectionPolicy().valueFromQuery(targetQuery, subRow, sourceQuery.getSession()); |
| if (object instanceof Collection) { |
| java.util.Iterator<?> iter = ((Collection<?>)object).iterator(); |
| while (iter.hasNext()) { |
| cp.addInto(iter.next(), results, executionSession); |
| } |
| } else if (object instanceof java.util.Map) { |
| java.util.Iterator<?> iter = ((java.util.Map<?, ?>)object).values().iterator(); |
| while (iter.hasNext()) { |
| cp.addInto(iter.next(), results, executionSession); |
| } |
| } else { |
| cp.addInto(object, results, executionSession); |
| } |
| } |
| if (cp.sizeFor(results) == 0) { |
| return null; |
| } |
| return results; |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @SuppressWarnings({"unchecked"}) |
| public Vector getForeignKeyRows(AbstractRecord row, AbstractSession session) { |
| Vector<AbstractRecord> subRows = new Vector<>(); |
| if (getForeignKeyGroupingElement() == null) { |
| if (this.getSourceForeignKeyFields().size() > 0) { |
| Object values = row.getValues(this.getSourceForeignKeyFields().get(0)); |
| |
| if (values != null) { |
| if (values instanceof Vector) { |
| Vector<?> vals = (Vector<?>) values; |
| int valuesSize = vals.size(); |
| for (int j = 0; j < valuesSize; j++) { |
| AbstractRecord newRecord = this.descriptor.getObjectBuilder().createRecord(session); |
| newRecord.put(this.getSourceForeignKeyFields().get(0), vals.get(j)); |
| subRows.add(newRecord); |
| } |
| } else { |
| AbstractRecord newRecord = this.descriptor.getObjectBuilder().createRecord(session); |
| newRecord.put(getSourceForeignKeyFields().get(0), values); |
| subRows.add(newRecord); |
| } |
| } |
| } |
| } else { |
| subRows = (Vector<AbstractRecord>)row.getValues(getForeignKeyGroupingElement()); |
| } |
| return subRows; |
| } |
| |
| /** |
| * INTERNAL: |
| * Get the appropriate attribute value from the object |
| * and put it in the appropriate field of the database row. |
| * Loop through the reference objects and extract the |
| * primary keys and put them in the vector of "nested" rows. |
| */ |
| @Override |
| public void writeFromObjectIntoRow(Object object, AbstractRecord row, AbstractSession session, WriteType writeType) { |
| if (!isForeignKeyRelationship) { |
| return; |
| } |
| |
| if (((getSourceForeignKeysToTargetKeys()) == null) || (getSourceForeignKeysToTargetKeys().size() == 0)) { |
| return; |
| } |
| |
| if (this.isReadOnly()) { |
| return; |
| } |
| |
| AbstractRecord referenceRow = this.getIndirectionPolicy().extractReferenceRow(this.getAttributeValueFromObject(object)); |
| if (referenceRow != null) { |
| // the reference objects have not been instantiated - use the value from the original row |
| if (getForeignKeyGroupingElement() != null) { |
| row.put(this.getForeignKeyGroupingElement(), referenceRow.getValues(this.getForeignKeyGroupingElement())); |
| } else if (getSourceForeignKeyFields().size() > 0) { |
| DatabaseField foreignKeyField = getSourceForeignKeyFields().get(0); |
| row.put(foreignKeyField, referenceRow.getValues(foreignKeyField)); |
| } |
| return; |
| } |
| |
| ContainerPolicy cp = this.getContainerPolicy(); |
| |
| // extract the keys from the objects |
| Object attributeValue = this.getRealCollectionAttributeValueFromObject(object, session); |
| |
| if (getForeignKeyGroupingElement() != null) { |
| Vector<AbstractRecord> nestedRows = new Vector<>(cp.sizeFor(attributeValue)); |
| for (Object iter = cp.iteratorFor(attributeValue); cp.hasNext(iter);) { |
| AbstractRecord nestedRow = extractKeyRowFromReferenceObject(cp.next(iter, session), session, row); |
| nestedRows.add(nestedRow); |
| } |
| row.add(this.getForeignKeyGroupingElement(), nestedRows); |
| } else { |
| DatabaseField singleField = getSourceForeignKeyFields().get(0); |
| DatabaseField pkField = getSourceForeignKeysToTargetKeys().get(singleField); |
| List<Object> foreignKeys = new ArrayList<>(cp.sizeFor(attributeValue)); |
| for (Object iter = cp.iteratorFor(attributeValue); cp.hasNext(iter);) { |
| Object singleValue = getReferenceDescriptor().getObjectBuilder().extractValueFromObjectForField(cp.next(iter, session), pkField, session); |
| foreignKeys.add(singleValue); |
| } |
| row.add(singleField, foreignKeys); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This row is built for shallow insert which happens in case of bidirectional inserts. |
| * The foreign keys must be set to null to avoid constraints. |
| */ |
| @Override |
| public void writeFromObjectIntoRowForShallowInsert(Object object, AbstractRecord row, AbstractSession session) { |
| if (isForeignKeyRelationship() && !isReadOnly()) { |
| if (getForeignKeyGroupingElement() != null) { |
| row.put(getForeignKeyGroupingElement(), null); |
| } else if (this.getSourceForeignKeyFields().size() > 0) { |
| row.put(getSourceForeignKeyFields().get(0), null); |
| } |
| } else { |
| super.writeFromObjectIntoRowForShallowInsert(object, row, session); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This row is built for update after shallow insert which happens in case of bidirectional inserts. |
| * It contains the foreign keys with non null values that were set to null for shallow insert. |
| * If mapping overrides writeFromObjectIntoRowForShallowInsert method it must override this one, too. |
| */ |
| @Override |
| public void writeFromObjectIntoRowForUpdateAfterShallowInsert(Object object, AbstractRecord row, AbstractSession session, DatabaseTable table) { |
| if (isReadOnly() || !isForeignKeyRelationship()) { |
| return; |
| } |
| if (getForeignKeyGroupingElement() != null) { |
| if (!getForeignKeyGroupingElement().getTable().equals(table)) { |
| return; |
| } |
| } else if (this.getSourceForeignKeyFields().size() > 0) { |
| if (!getSourceForeignKeyFields().get(0).getTable().equals(table)) { |
| return; |
| } |
| } |
| writeFromObjectIntoRow(object, row, session, WriteType.UPDATE); |
| } |
| |
| /** |
| * INTERNAL: |
| * This row is built for shallow insert which happens in case of bidirectional inserts. |
| * The foreign keys must be set to null to avoid constraints. |
| */ |
| @Override |
| public void writeFromObjectIntoRowForShallowInsertWithChangeRecord(ChangeRecord changeRecord, AbstractRecord row, AbstractSession session) { |
| if (isForeignKeyRelationship() && !isReadOnly()) { |
| if (getForeignKeyGroupingElement() != null) { |
| row.put(getForeignKeyGroupingElement(), null); |
| } else if (this.getSourceForeignKeyFields().size() > 0) { |
| row.put(getSourceForeignKeyFields().get(0), null); |
| } |
| } else { |
| super.writeFromObjectIntoRowForShallowInsertWithChangeRecord(changeRecord, row, session); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * If any of the references objects has changed, write out |
| * all the keys. |
| */ |
| @Override |
| public void writeFromObjectIntoRowForUpdate(WriteObjectQuery writeQuery, AbstractRecord row) throws DescriptorException { |
| if (!this.isAttributeValueInstantiatedOrChanged(writeQuery.getObject())) { |
| return; |
| } |
| |
| AbstractSession session = writeQuery.getSession(); |
| |
| if (session.isUnitOfWork()) { |
| // PRS2074 fix for "traditional" Indirection |
| Object collection1 = this.getRealCollectionAttributeValueFromObject(writeQuery.getObject(), session); |
| Object collection2 = this.getRealCollectionAttributeValueFromObject(writeQuery.getBackupClone(), session); |
| if (this.compareObjectsWithoutPrivateOwned(collection1, collection2, session)) { |
| return;// nothing has changed - don't put anything in the row |
| } |
| } |
| this.writeFromObjectIntoRow(writeQuery.getObject(), row, session, WriteType.UPDATE); |
| |
| } |
| |
| /** |
| * INTERNAL: |
| * Get the appropriate attribute value from the object |
| * and put it in the appropriate field of the database row. |
| * Loop through the reference objects and extract the |
| * primary keys and put them in the vector of "nested" rows. |
| */ |
| @Override |
| public void writeFromObjectIntoRowWithChangeRecord(ChangeRecord changeRecord, AbstractRecord row, AbstractSession session, WriteType writeType) { |
| if (isForeignKeyRelationship()) { |
| Object object = ((ObjectChangeSet)changeRecord.getOwner()).getUnitOfWorkClone(); |
| this.writeFromObjectIntoRow(object, row, session, writeType); |
| } else { |
| super.writeFromObjectIntoRowWithChangeRecord(changeRecord, row, session, writeType); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Write fields needed for insert into the template for with null values. |
| */ |
| @Override |
| public void writeInsertFieldsIntoRow(AbstractRecord row, AbstractSession session) { |
| if (isForeignKeyRelationship() && !isReadOnly()) { |
| if (getForeignKeyGroupingElement() != null) { |
| row.put(getForeignKeyGroupingElement(), null); |
| } else if (this.getSourceForeignKeyFields().size() > 0) { |
| row.put(getSourceForeignKeyFields().get(0), null); |
| } |
| } else { |
| super.writeInsertFieldsIntoRow(row, session); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is not supported in an EIS environment. |
| */ |
| @Override |
| public void setSelectionSQLString(String sqlString) { |
| throw DescriptorException.invalidMappingOperation(this, "setSelectionSQLString"); |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is not supported in an EIS environment. |
| */ |
| @Override |
| public void setDeleteAllSQLString(String sqlString) { |
| throw DescriptorException.invalidMappingOperation(this, "setDeleteAllSQLString"); |
| } |
| } |