blob: 430162020e753c80a3e6f340c211f4283c925b8e [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.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() != 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
public Object clone() {
EISOneToManyMapping clone = (EISOneToManyMapping)super.clone();
clone.setSourceForeignKeysToTargetKeys((Map)((HashMap)getSourceForeignKeysToTargetKeys()).clone());
return clone;
}
/**
* Return all the fields mapped by the mapping.
*/
@Override
protected Vector collectFields() {
if (isForeignKeyRelationship()) {
if (this.getForeignKeyGroupingElement() != null) {
Vector 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 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:
*/
public Vector getForeignKeyRows(AbstractRecord row, AbstractSession session) {
Vector 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) {
int valuesSize = ((Vector)values).size();
for (int j = 0; j < valuesSize; j++) {
AbstractRecord newRecord = this.descriptor.getObjectBuilder().createRecord(session);
newRecord.put(this.getSourceForeignKeyFields().get(0), ((Vector)values).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)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);
Vector nestedRows = new Vector(cp.sizeFor(attributeValue));
if (getForeignKeyGroupingElement() != null) {
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 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");
}
}