org.eclipse.persistence.eis.mappings.EISOneToManyMapping Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* 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;
/**
* 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.
*
*
* Record formats
*
* Record Type
* Description
*
*
* Indexed
* Ordered collection of record elements. The indexed record EIS format
* enables Java class attribute values to be retreived by position or index.
*
*
* Mapped
* 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.
*
*
* XML
* Record/Map representation of an XML DOM element.
*
*
*
* @see org.eclipse.persistence.eis.EISDescriptor#useIndexedRecordFormat
* @see org.eclipse.persistence.eis.EISDescriptor#useMappedRecordFormat
* @see org.eclipse.persistence.eis.EISDescriptor#useXMLRecordFormat
*
* @since Oracle TopLink 10g 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 targetForeignKeyFields;
/** The (typically primary) source key fields that are referenced by the targetForeignKeyFields. */
protected transient List sourceForeignKeyFields;
/** This maps the source foreign key fields to the corresponding (primary) target key fields. */
protected transient Map 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 getSourceForeignKeyFields() {
return sourceForeignKeyFields;
}
/**
* INTERNAL:
* Sets the source foreign key fields.
*/
public void setSourceForeignKeyFields(List fields) {
sourceForeignKeyFields = fields;
if ((fields != null) && (fields.size() > 0)) {
this.setIsForeignKeyRelationship(true);
}
}
/**
* INTERNAL:
* Return the source foreign key fields.
*/
public List getTargetForeignKeyFields() {
return targetForeignKeyFields;
}
/**
* INTERNAL:
* Sets the target foreign key fields.
*/
public void setTargetForeignKeyFields(List fields) {
targetForeignKeyFields = fields;
}
/**
* INTERNAL:
* Sets the target foreign key fields.
*/
public Map getSourceForeignKeysToTargetKeys() {
return sourceForeignKeysToTargetKeys;
}
/**
* INTERNAL:
* Set the source keys to target keys fields association.
*/
public void setSourceForeignKeysToTargetKeys(Map 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 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)((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:
*/
@SuppressWarnings({"unchecked"})
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) {
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)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 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