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

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

The newest version!
/*
 * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
//     01/15/2015-2.6 Mythily Parthasarathy
//       - 457480: NPE in  MethodAttributeAccessor.getAttributeValueFromObject
package org.eclipse.persistence.mappings;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.changetracking.ChangeTracker;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.OptimisticLockException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.indirection.ValueHolder;
import org.eclipse.persistence.indirection.ValueHolderInterface;
import org.eclipse.persistence.internal.descriptors.DescriptorIterator;
import org.eclipse.persistence.internal.descriptors.ObjectBuilder;
import org.eclipse.persistence.internal.descriptors.changetracking.ObjectChangeListener;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.IdentityHashSet;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.internal.indirection.DatabaseValueHolder;
import org.eclipse.persistence.internal.indirection.ProxyIndirectionPolicy;
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.ObjectReferenceChangeRecord;
import org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.internal.sessions.remote.ObjectDescriptor;
import org.eclipse.persistence.queries.DeleteObjectQuery;
import org.eclipse.persistence.queries.InsertObjectQuery;
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.ReadObjectQuery;
import org.eclipse.persistence.queries.WriteObjectQuery;
import org.eclipse.persistence.sessions.CopyGroup;
import org.eclipse.persistence.sessions.Project;
import org.eclipse.persistence.sessions.remote.DistributedSession;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 

Purpose: Abstract class for 1:1, variable 1:1 and reference mappings */ public abstract class ObjectReferenceMapping extends ForeignReferenceMapping { /** Keeps track if any of the fields are foreign keys. */ protected boolean isForeignKeyRelationship; /** Keeps track of which fields are foreign keys on a per field basis (can have mixed foreign key relationships). */ protected List foreignKeyFields; protected ObjectReferenceMapping() { super(); this.setWeight(WEIGHT_TO_ONE); } /** * INTERNAL: * Used during building the backup shallow copy to copy the vector without re-registering the target objects. * For 1-1 or ref the reference is from the clone so it is already registered. */ @Override public Object buildBackupCloneForPartObject(Object attributeValue, Object clone, Object backup, UnitOfWorkImpl unitOfWork) { return attributeValue; } /** * INTERNAL: * Require for cloning, the part must be cloned. * Ignore the objects, use the attribute value. */ @Override public Object buildCloneForPartObject(Object attributeValue, Object original, CacheKey cacheKey, Object clone, AbstractSession cloningSession, Integer refreshCascade, boolean isExisting, boolean isFromSharedCache) { if (attributeValue == null) { return null; } if (cloningSession.isUnitOfWork()){ return buildUnitofWorkCloneForPartObject(attributeValue, original, clone, refreshCascade, (UnitOfWorkImpl)cloningSession, isExisting); } // Not a unit Of Work clone so must have been a PROTECTED object if (this.referenceDescriptor.getCachePolicy().isProtectedIsolation()) { ClassDescriptor descriptor = this.referenceDescriptor; if (descriptor.hasInterfacePolicy()){ descriptor = cloningSession.getClassDescriptor(attributeValue.getClass()); } return cloningSession.createProtectedInstanceFromCachedData(attributeValue, refreshCascade, descriptor); } return attributeValue; } /** * INTERNAL: * Require for cloning, the part must be cloned. * Ignore the objects, use the attribute value. */ public Object buildUnitofWorkCloneForPartObject(Object attributeValue, Object original, Object clone, Integer refreshCascade, UnitOfWorkImpl unitOfWork, boolean isExisting) { if (attributeValue == null) { return null; } if (refreshCascade != null ){ return switch (refreshCascade) { case ObjectBuildingQuery.CascadeAllParts -> unitOfWork.mergeClone(attributeValue, MergeManager.CASCADE_ALL_PARTS, true); case ObjectBuildingQuery.CascadePrivateParts -> unitOfWork.mergeClone(attributeValue, MergeManager.CASCADE_PRIVATE_PARTS, true); case ObjectBuildingQuery.CascadeByMapping -> unitOfWork.mergeClone(attributeValue, MergeManager.CASCADE_BY_MAPPING, true); default -> unitOfWork.mergeClone(attributeValue, MergeManager.NO_CASCADE, true); }; }else{ // Optimize registration to knowledge of existence. Object registeredObject = null; if (isExisting) { registeredObject = unitOfWork.registerExistingObject(attributeValue, true); } else { // Not known whether existing or not. registeredObject = unitOfWork.registerObject(attributeValue); // if the mapping is privately owned, keep track of the privately owned reference in the UnitOfWork if (isCandidateForPrivateOwnedRemoval() && unitOfWork.shouldDiscoverNewObjects() && registeredObject != null && unitOfWork.isCloneNewObject(registeredObject)) { unitOfWork.addPrivateOwnedObject(this, registeredObject); } } return registeredObject; } } /** * INTERNAL: * Copy of the attribute of the object. * This is NOT used for unit of work but for templatizing an object. */ @Override public void buildCopy(Object copy, Object original, CopyGroup group) { Object attributeValue = getRealAttributeValueFromObject(original, group.getSession()); if ((attributeValue != null) && (group.shouldCascadeAllParts() || (group.shouldCascadePrivateParts() && isPrivateOwned()) || group.shouldCascadeTree())) { attributeValue = group.getSession().copyInternal(attributeValue, group); } else if (attributeValue != null) { // Check for copy of part, i.e. back reference. Object copyValue = group.getCopies().get(attributeValue); if (copyValue != null) { attributeValue = copyValue; } } // if value holder is used, then the value holder shared with original substituted for a new ValueHolder. getIndirectionPolicy().reset(copy); setRealAttributeValueInObject(copy, attributeValue); } /** * INTERNAL: * In case Query By Example is used, this method generates an expression from a attribute value pair. Since * this is a ObjectReference mapping, a recursive call is made to the buildExpressionFromExample method of * ObjectBuilder. */ @Override public Expression buildExpression(Object queryObject, QueryByExamplePolicy policy, Expression expressionBuilder, Map processedObjects, AbstractSession session) { String attributeName = this.getAttributeName(); Object attributeValue = this.getRealAttributeValueFromObject(queryObject, session); if (!policy.shouldIncludeInQuery(queryObject.getClass(), attributeName, attributeValue)) { //the attribute name and value pair is not to be included in the query. return null; } if (attributeValue == null) { //even though it is null, it is to be always included in the query Expression expression = expressionBuilder.get(attributeName); return policy.completeExpressionForNull(expression); } ObjectBuilder objectBuilder = getReferenceDescriptor().getObjectBuilder(); return objectBuilder.buildExpressionFromExample(attributeValue, policy, expressionBuilder.get(attributeName), processedObjects, session); } /** * INTERNAL: * Return an ObjectReferenceChangeRecord describing the change, or null if no change. * Used to compute changes for deferred change tracking. */ @Override public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession session) { Object cloneAttribute = null; Object backUpAttribute = null; cloneAttribute = getAttributeValueFromObject(clone); if (!owner.isNew()) { backUpAttribute = getAttributeValueFromObject(backUp); if ((backUpAttribute == null) && (cloneAttribute == null)) { return null; } } if ((cloneAttribute != null) && (!this.indirectionPolicy.objectIsInstantiated(cloneAttribute))) { //the clone's valueholder was never triggered so there will be no change return null; } Object cloneAttributeValue = null; Object backUpAttributeValue = null; if (cloneAttribute != null) { cloneAttributeValue = getRealAttributeValueFromAttribute(cloneAttribute, clone, session); } if (backUpAttribute != null) { backUpAttributeValue = getRealAttributeValueFromAttribute(backUpAttribute, backUp, session); } if ((cloneAttributeValue == backUpAttributeValue) && (!owner.isNew())) {// if it is new record the value return null; } ObjectReferenceChangeRecord record = internalBuildChangeRecord(cloneAttributeValue, owner, session); if (!owner.isNew()) { record.setOldValue(backUpAttributeValue); } return record; } /** * INTERNAL: * Directly build a change record based on the newValue without comparison */ public ObjectReferenceChangeRecord internalBuildChangeRecord(Object newValue, ObjectChangeSet owner, AbstractSession session) { ObjectReferenceChangeRecord changeRecord = new ObjectReferenceChangeRecord(owner); changeRecord.setAttribute(getAttributeName()); changeRecord.setMapping(this); setNewValueInChangeRecord(newValue, changeRecord, owner, session); return changeRecord; } /** * INTERNAL: * Set the newValue in the change record */ public void setNewValueInChangeRecord(Object newValue, ObjectReferenceChangeRecord changeRecord, ObjectChangeSet owner, AbstractSession session) { if (newValue != null) { // Bug 2612571 - added more flexible manner of getting descriptor ObjectChangeSet newSet = getDescriptorForTarget(newValue, session).getObjectBuilder().createObjectChangeSet(newValue, (UnitOfWorkChangeSet)owner.getUOWChangeSet(), session); changeRecord.setNewValue(newSet); } else { changeRecord.setNewValue(null); } } /** * INTERNAL: * Compare the references of the two objects are the same, not the objects themselves. * Used for independent relationships. * This is used for testing and validation purposes. */ @Override protected boolean compareObjectsWithoutPrivateOwned(Object firstObject, Object secondObject, AbstractSession session) { Object firstReferencedObject = getRealAttributeValueFromObject(firstObject, session); Object secondReferencedObject = getRealAttributeValueFromObject(secondObject, session); if ((firstReferencedObject == null) && (secondReferencedObject == null)) { return true; } if ((firstReferencedObject == null) || (secondReferencedObject == null)) { return false; } Object firstKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(firstReferencedObject, session); Object secondKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(secondReferencedObject, session); if (firstKey == null) { if (secondKey == null) { return true; } return false; } return firstKey.equals(secondKey); } /** * INTERNAL: * Compare the references of the two objects are the same, and the objects themselves are the same. * Used for private relationships. * This is used for testing and validation purposes. */ @Override protected boolean compareObjectsWithPrivateOwned(Object firstObject, Object secondObject, AbstractSession session) { Object firstPrivateObject = getRealAttributeValueFromObject(firstObject, session); Object secondPrivateObject = getRealAttributeValueFromObject(secondObject, session); return session.compareObjects(firstPrivateObject, secondPrivateObject); } /** * INTERNAL: * We are not using a remote valueholder * so we need to replace the reference object(s) with * the corresponding object(s) from the remote session. *

* ObjectReferenceMappings need to unwrap and wrap the * reference object. */ @Override public void fixRealObjectReferences(Object object, Map objectDescriptors, Map processedObjects, ObjectLevelReadQuery query, DistributedSession session) { //bug 4147755 getRealAttribute... / setReal... Object attributeValue = getRealAttributeValueFromObject(object, session); attributeValue = getReferenceDescriptor().getObjectBuilder().unwrapObject(attributeValue, session); ObjectLevelReadQuery tempQuery = query; if (!tempQuery.shouldMaintainCache()) { if ((!tempQuery.shouldCascadeParts()) || (tempQuery.shouldCascadePrivateParts() && (!isPrivateOwned()))) { tempQuery = null; } } Object remoteAttributeValue = session.getObjectCorrespondingTo(attributeValue, objectDescriptors, processedObjects, tempQuery); remoteAttributeValue = getReferenceDescriptor().getObjectBuilder().wrapObject(remoteAttributeValue, session); setRealAttributeValueInObject(object, remoteAttributeValue); } /** * INTERNAL: * Return a descriptor for the target of this mapping * @see org.eclipse.persistence.mappings.VariableOneToOneMapping * Bug 2612571 */ public ClassDescriptor getDescriptorForTarget(Object object, AbstractSession session) { return session.getDescriptor(object); } /** * INTERNAL: * Object reference must unwrap the reference object if required. */ @Override public Object getRealAttributeValueFromAttribute(Object attributeValue, Object object, AbstractSession session) { Object value = super.getRealAttributeValueFromAttribute(attributeValue, object, session); value = getReferenceDescriptor().getObjectBuilder().unwrapObject(value, session); return value; } /** * INTERNAL: * Related mapping should implement this method to return true. */ @Override public boolean isObjectReferenceMapping() { return true; } /** * INTERNAL: * Iterate on the attribute value. * The value holder has already been processed. */ @Override public void iterateOnRealAttributeValue(DescriptorIterator iterator, Object realAttributeValue) { // This may be wrapped as the caller in iterate on foreign reference does not unwrap as the type is generic. Object unwrappedAttributeValue = getReferenceDescriptor().getObjectBuilder().unwrapObject(realAttributeValue, iterator.getSession()); iterator.iterateReferenceObjectForMapping(unwrappedAttributeValue, this); } /** * Force instantiation of all indirections. */ @Override public void loadAll(Object object, AbstractSession session, IdentityHashSet loaded) { Object value = getRealAttributeValueFromObject(object, session); if (value != null) { getReferenceDescriptor().getObjectBuilder().loadAll(value, session, loaded); } } /** * INTERNAL: * Merge changes from the source to the target object. Which is the original from the parent UnitOfWork */ @Override public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager, AbstractSession targetSession) { if (this.descriptor.getCachePolicy().isProtectedIsolation()&& !this.isCacheable && !targetSession.isProtectedSession()){ setAttributeValueInObject(target, this.indirectionPolicy.buildIndirectObject(new ValueHolder<>(null))); return; } Object targetValueOfSource = null; // The target object must be completely merged before setting it otherwise // another thread can pick up the partial object. if (shouldMergeCascadeParts(mergeManager)) { ObjectChangeSet set = (ObjectChangeSet)((ObjectReferenceChangeRecord)changeRecord).getNewValue(); if (set != null) { if (mergeManager.shouldMergeChangesIntoDistributedCache()) { //Let's try and find it first. We may have merged it already. In which case merge //changes will stop the recursion targetValueOfSource = set.getTargetVersionOfSourceObject(mergeManager, targetSession, false); if ((targetValueOfSource == null) && (set.isNew() || set.isAggregate()) && set.containsChangesFromSynchronization()) { if (!mergeManager.isAlreadyMerged(set, targetSession)) { // if we haven't merged this object already then build a new object // otherwise leave it as null which will stop the recursion // CR 2855 // CR 3424 Need to build the right instance based on class type instead of refernceDescriptor Class objectClass = set.getClassType(mergeManager.getSession()); targetValueOfSource = mergeManager.getSession().getDescriptor(objectClass).getObjectBuilder().buildNewInstance(); //Store the changeset to prevent us from creating this new object again mergeManager.recordMerge(set, targetValueOfSource, targetSession); } else { //CR 4012 //we have all ready created the object, must be in a cyclic //merge on a new object so get it out of the already merged collection targetValueOfSource = mergeManager.getMergedObject(set, targetSession); } } else { // If We have not found it anywhere else load it from the database targetValueOfSource = set.getTargetVersionOfSourceObject(mergeManager, targetSession, true); } if (set.containsChangesFromSynchronization()) { mergeManager.mergeChanges(targetValueOfSource, set, targetSession); } //bug:3604593 - ensure reference not changed source is invalidated if target object not found if (targetValueOfSource == null) { mergeManager.getSession().getIdentityMapAccessorInstance().invalidateObject(target); return; } } else { mergeManager.mergeChanges(set.getUnitOfWorkClone(), set, targetSession); } } } if ((targetValueOfSource == null) && (((ObjectReferenceChangeRecord)changeRecord).getNewValue() != null)) { targetValueOfSource = ((ObjectChangeSet)((ObjectReferenceChangeRecord)changeRecord).getNewValue()).getTargetVersionOfSourceObject(mergeManager, targetSession); } // Register new object in nested units of work must not be registered into the parent, // so this records them in the merge to parent case. if (isPrivateOwned() && (source != null)) { mergeManager.registerRemovedNewObjectIfRequired(getRealAttributeValueFromObject(source, mergeManager.getSession())); } targetValueOfSource = getReferenceDescriptor().getObjectBuilder().wrapObject(targetValueOfSource, targetSession); // if value holder is used, then the value holder shared with original substituted for a new ValueHolder. getIndirectionPolicy().reset(target); setRealAttributeValueInObject(target, targetValueOfSource); } /** * INTERNAL: * Merge changes from the source to the target object. */ @Override public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager, AbstractSession targetSession) { if (this.descriptor.getCachePolicy().isProtectedIsolation()&& !this.isCacheable && !targetSession.isProtectedSession()){ setAttributeValueInObject(target, this.indirectionPolicy.buildIndirectObject(new ValueHolder<>(null))); return; } if (isTargetUnInitialized) { // This will happen if the target object was removed from the cache before the commit was attempted, // or for new objects. if (mergeManager.shouldMergeWorkingCopyIntoOriginal()) { if (!isAttributeValueInstantiated(source)) { setAttributeValueInObject(target, this.indirectionPolicy.getOriginalIndirectionObject(getAttributeValueFromObject(source), targetSession)); return; } else { // Must clear the old value holder to cause it to be reset. this.indirectionPolicy.reset(target); } } } if (!shouldMergeCascadeReference(mergeManager)) { // This is only going to happen on mergeClone, and we should not attempt to merge the reference return; } if (mergeManager.shouldRefreshRemoteObject() && usesIndirection()) { mergeRemoteValueHolder(target, source, mergeManager); return; } if (mergeManager.isForRefresh()) { if (!isAttributeValueInstantiated(target)) { // This will occur when the clone's value has not been instantiated yet and we do not need // the refresh that attribute if (shouldRefreshCascadeParts(mergeManager)){ Object attributeValue = getAttributeValueFromObject(source); Integer refreshCascade = null; if (selectionQuery != null && selectionQuery.isObjectBuildingQuery() && ((ObjectBuildingQuery)selectionQuery).shouldRefreshIdentityMapResult()){ refreshCascade = selectionQuery.getCascadePolicy(); } Object clonedAttributeValue = this.indirectionPolicy.cloneAttribute(attributeValue, source, null, target, refreshCascade, mergeManager.getSession(), false); // building clone from an original not a row. setAttributeValueInObject(target, clonedAttributeValue); } return; } } else if (!isAttributeValueInstantiated(source)) { // I am merging from a clone into an original. No need to do merge if the attribute was never // modified return; } Object valueOfSource = getRealAttributeValueFromObject(source, mergeManager.getSession()); Object targetValueOfSource = null; // The target object must be completely merged before setting it otherwise // another thread can pick up the partial object. if (shouldMergeCascadeParts(mergeManager) && (valueOfSource != null)) { if ((mergeManager.getSession().isUnitOfWork()) && (((UnitOfWorkImpl)mergeManager.getSession()).getUnitOfWorkChangeSet() != null)) { // If it is a unit of work, we have to check if I have a change Set fot this object Object targetValue = mergeManager.mergeChanges(mergeManager.getObjectToMerge(valueOfSource, referenceDescriptor, targetSession), (ObjectChangeSet) ((UnitOfWorkImpl)mergeManager.getSession()).getUnitOfWorkChangeSet().getObjectChangeSetForClone(valueOfSource), targetSession); if (target == source && targetValue != valueOfSource && (this.descriptor.getObjectChangePolicy().isObjectChangeTrackingPolicy()) && (target instanceof ChangeTracker) && (((ChangeTracker)target)._persistence_getPropertyChangeListener() != null)) { ObjectChangeListener listener = (ObjectChangeListener)((ChangeTracker)target)._persistence_getPropertyChangeListener(); if (listener != null){ //update the ChangeSet recorded within the parents ObjectChangeSet as the parent is referenceing the ChangeSet //for a detached or new Entity. this.descriptor.getObjectChangePolicy().updateListenerForSelfMerge(listener, this, valueOfSource, targetValue, (UnitOfWorkImpl) mergeManager.getSession()); } } } else { mergeManager.mergeChanges(mergeManager.getObjectToMerge(valueOfSource, referenceDescriptor, targetSession), null, targetSession); } } if (valueOfSource != null) { // Need to do this after merge so that an object exists in the database targetValueOfSource = mergeManager.getTargetVersionOfSourceObject(valueOfSource, referenceDescriptor, targetSession); } // If merge into the unit of work, must only merge and raise the event is the value changed. if ((mergeManager.shouldMergeCloneIntoWorkingCopy() || mergeManager.shouldMergeCloneWithReferencesIntoWorkingCopy()) && !mergeManager.isForRefresh() && this.descriptor.getObjectChangePolicy().isObjectChangeTrackingPolicy()) { // Object level or attribute level so lets see if we need to raise the event? Object valueOfTarget = getRealAttributeValueFromObject(target, mergeManager.getSession()); if (valueOfTarget != targetValueOfSource) { //equality comparison cause both are uow clones this.descriptor.getObjectChangePolicy().raiseInternalPropertyChangeEvent(target, getAttributeName(), valueOfTarget, targetValueOfSource); } else { // No change. return; } } targetValueOfSource = this.referenceDescriptor.getObjectBuilder().wrapObject(targetValueOfSource, mergeManager.getSession()); setRealAttributeValueInObject(target, targetValueOfSource); } /** * INTERNAL: * Return all the fields populated by this mapping, these are foreign keys only. */ @Override protected List collectFields() { return getForeignKeyFields(); } /** * INTERNAL: * Returns the foreign key names associated with the mapping. * These are the fields that will be populated by the 1-1 mapping when writing. */ public List getForeignKeyFields() { return foreignKeyFields; } /** * INTERNAL: * Set the foreign key fields associated with the mapping. * These are the fields that will be populated by the 1-1 mapping when writing. */ protected void setForeignKeyFields(List foreignKeyFields) { this.foreignKeyFields = foreignKeyFields; if (!foreignKeyFields.isEmpty()) { setIsForeignKeyRelationship(true); } } /** * INTERNAL: * Return if the 1-1 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-1 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; } /** * INTERNAL: * Insert privately owned parts */ @Override public void preInsert(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { if (isForeignKeyRelationship()) { insert(query); } } /** * INTERNAL: * Reads the private owned object. */ protected Object readPrivateOwnedForObject(ObjectLevelModifyQuery modifyQuery) throws DatabaseException { if (modifyQuery.getSession().isUnitOfWork()) { if (modifyQuery.getObjectChangeSet() != null) { ObjectReferenceChangeRecord record = (ObjectReferenceChangeRecord) modifyQuery.getObjectChangeSet().getChangesForAttributeNamed(getAttributeName()); if (record != null) { return record.getOldValue(); } } else { // Old commit. return getRealAttributeValueFromObject(modifyQuery.getBackupClone(), modifyQuery.getSession()); } } return null; } /** * INTERNAL: * Update privately owned parts */ @Override public void preUpdate(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { if (!isAttributeValueInstantiated(query.getObject())) { return; } if (isPrivateOwned()) { Object objectInDatabase = readPrivateOwnedForObject(query); if (objectInDatabase != null) { query.setProperty(this, objectInDatabase); } } if (!isForeignKeyRelationship()) { return; } update(query); } /** * INTERNAL: * Overridden by mappings that require additional processing of the change record after the record has been calculated. */ @Override public void postCalculateChanges(org.eclipse.persistence.sessions.changesets.ChangeRecord changeRecord, UnitOfWorkImpl uow) { // no need for private owned check. This code is only registered for private owned mappings. // targets are added to and/or removed to/from the source. Object oldValue = changeRecord.getOldValue(); if (oldValue != null) { uow.addDeletedPrivateOwnedObjects(this, oldValue); } } /** * INTERNAL: * Overridden by mappings that require additional processing of the change record after the record has been calculated. */ @Override public void recordPrivateOwnedRemovals(Object object, UnitOfWorkImpl uow) { Object target = getRealAttributeValueFromObject(object, uow); if (target != null){ this.referenceDescriptor.getObjectBuilder().recordPrivateOwnedRemovals(target, uow, false); } } /** * INTERNAL: * Delete privately owned parts */ @Override public void postDelete(DeleteObjectQuery query) throws DatabaseException, OptimisticLockException { // Deletion takes place only if it has privately owned parts and mapping is not read only. if (!shouldObjectModifyCascadeToParts(query)) { return; } Object object = query.getProperty(this); // The object is stored in the query by preDeleteForObjectUsing(...). if (isForeignKeyRelationship()) { if (object != null) { query.removeProperty(this); AbstractSession session = query.getSession(); //if the query is being passed from an aggregate collection descriptor then // The delete will have been cascaded at update time. This will cause sub objects // to be ignored, and real only classes to throw exceptions. // If it is an aggregate Collection then delay deletes until they should be deleted //CR 2811 if (query.isCascadeOfAggregateDelete()) { session.getCommitManager().addObjectToDelete(object); } else { // PERF: Avoid query execution if already deleted. if (session.getCommitManager().isCommitCompletedInPostOrIgnore(object)) { return; } if (this.isCascadeOnDeleteSetOnDatabase && !hasRelationTableMechanism() && session.isUnitOfWork()) { ((UnitOfWorkImpl)session).getCascadeDeleteObjects().add(object); } DeleteObjectQuery deleteQuery = new DeleteObjectQuery(); deleteQuery.setIsExecutionClone(true); deleteQuery.setObject(object); deleteQuery.setCascadePolicy(query.getCascadePolicy()); session.executeQuery(deleteQuery); } } } } /** * INTERNAL: * Insert privately owned parts */ @Override public void postInsert(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { if (!isForeignKeyRelationship()) { insert(query); } } /** * INTERNAL: * Update privately owned parts */ @Override public void postUpdate(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { if (!isAttributeValueInstantiated(query.getObject())) { return; } if (!isForeignKeyRelationship()) { update(query); } // If a private owned reference was changed the old value will be set on the query as a property. Object objectInDatabase = query.getProperty(this); if (objectInDatabase != null) { query.removeProperty(this); } else { return; } // If there is no change (old commit), it must be determined if the value changed. if (query.getObjectChangeSet() == null) { Object objectInMemory = getRealAttributeValueFromObject(query.getObject(), query.getSession()); // delete the object in the database if it is no more a referenced object. if (objectInDatabase != objectInMemory) { Object keyForObjectInDatabase = getPrimaryKeyForObject(objectInDatabase, query.getSession()); Object keyForObjectInMemory = null; if (objectInMemory != null) { keyForObjectInMemory = getPrimaryKeyForObject(objectInMemory, query.getSession()); } if ((keyForObjectInMemory != null) && keyForObjectInDatabase.equals(keyForObjectInMemory)) { return; } } else { return; } } if (!query.shouldCascadeOnlyDependentParts()) { query.getSession().deleteObject(objectInDatabase); } } /** * INTERNAL: * Delete privately owned parts */ @Override public void preDelete(DeleteObjectQuery query) throws DatabaseException, OptimisticLockException { // Deletion takes place according the the cascading policy if (!shouldObjectModifyCascadeToParts(query)) { return; } AbstractSession session = query.getSession(); // Get the privately owned parts. Object objectInMemory = getRealAttributeValueFromObject(query.getObject(), session); Object objectFromDatabase = null; // Because the value in memory may have been changed we check the previous value or database value. objectFromDatabase = readPrivateOwnedForObject(query); // If the value was changed, both values must be deleted (uow will have inserted the new one). if ((objectFromDatabase != null) && (objectFromDatabase != objectInMemory)) { // Also check pk as may not be maintaining identity. Object keyForObjectInMemory = null; Object keyForObjectInDatabase = getPrimaryKeyForObject(objectFromDatabase, session); if (objectInMemory != null) { keyForObjectInMemory = getPrimaryKeyForObject(objectInMemory, session); } if ((keyForObjectInMemory == null) || !keyForObjectInDatabase.equals(keyForObjectInMemory)) { if (this.isCascadeOnDeleteSetOnDatabase && !hasRelationTableMechanism() && session.isUnitOfWork()) { ((UnitOfWorkImpl)session).getCascadeDeleteObjects().add(objectFromDatabase); } DeleteObjectQuery deleteQuery = new DeleteObjectQuery(); deleteQuery.setIsExecutionClone(true); deleteQuery.setObject(objectFromDatabase); deleteQuery.setCascadePolicy(query.getCascadePolicy()); session.executeQuery(deleteQuery); } } if (!isForeignKeyRelationship()) { if (objectInMemory != null) { if (this.isCascadeOnDeleteSetOnDatabase && !hasRelationTableMechanism() && session.isUnitOfWork()) { ((UnitOfWorkImpl)session).getCascadeDeleteObjects().add(objectInMemory); } // PERF: Avoid query execution if already deleted. if (session.getCommitManager().isCommitCompletedInPostOrIgnore(objectInMemory)) { return; } DeleteObjectQuery deleteQuery = new DeleteObjectQuery(); deleteQuery.setIsExecutionClone(true); deleteQuery.setObject(objectInMemory); deleteQuery.setCascadePolicy(query.getCascadePolicy()); session.executeQuery(deleteQuery); } } else { // The actual deletion of part takes place in postDeleteForObjectUsing(...). if (objectInMemory != null) { query.setProperty(this, objectInMemory); } } } /** * INTERNAL: * Record deletion dependencies for foreign key constraints. * This is used during deletion to resolve deletion cycles. */ @Override public void earlyPreDelete(DeleteObjectQuery query, Object object) { AbstractSession session = query.getSession(); // Avoid instantiating objects. Object attributeValue = getAttributeValueFromObject(object); Object targetObject = null; if (!this.indirectionPolicy.objectIsInstantiated(attributeValue) && !this.indirectionPolicy.objectIsEasilyInstantiated(attributeValue)) { AbstractRecord referenceRow = this.indirectionPolicy.extractReferenceRow(attributeValue); targetObject = this.selectionQuery.checkEarlyReturn(session, referenceRow); } else { targetObject = getRealAttributeValueFromAttribute(attributeValue, object, session); } UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)session; if ((targetObject != null) && unitOfWork.getDeletedObjects().containsKey(targetObject)) { unitOfWork.addDeletionDependency(targetObject, object); } } /** * INTERNAL: * Cascade registerNew for Create through mappings that require the cascade */ @Override public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects){ cascadePerformRemoveIfRequired(object, uow, visitedObjects, true); } /** * INTERNAL: * Cascade remove through mappings that require the cascade. * @param object is either the source object, or attribute value if getAttributeValueFromObject is true. */ public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects, boolean getAttributeValueFromObject) { if (!this.cascadeRemove) { return; } Object attributeValue = null; if (getAttributeValueFromObject) { attributeValue = getAttributeValueFromObject(object); } else { attributeValue = object; } if (attributeValue != null) { if (getAttributeValueFromObject) { attributeValue = this.indirectionPolicy.getRealAttributeValueFromObject(object, attributeValue); } if (attributeValue != null && (! visitedObjects.containsKey(attributeValue)) ){ visitedObjects.put(attributeValue, attributeValue); if (this.isCascadeOnDeleteSetOnDatabase && !hasRelationTableMechanism()) { uow.getCascadeDeleteObjects().add(attributeValue); } uow.performRemove(attributeValue, visitedObjects); } } } /** * INTERNAL: * Cascade removal of orphaned private owned objects from the UnitOfWorkChangeSet */ @Override public void cascadePerformRemovePrivateOwnedObjectFromChangeSetIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { // if the object is not instantiated, do not instantiate or cascade Object attributeValue = getAttributeValueFromObject(object); if (attributeValue != null && this.indirectionPolicy.objectIsInstantiated(attributeValue)) { Object realValue = getRealAttributeValueFromObject(object, uow); if (!visitedObjects.containsKey(realValue)){ visitedObjects.put(realValue, realValue); // remove private owned object from UnitOfWork ChangeSet uow.performRemovePrivateOwnedObjectFromChangeSet(realValue, visitedObjects); } } } /** * INTERNAL: * This method is used to store the FK fields that can be cached that correspond to noncacheable mappings * the FK field values will be used to re-issue the query when cloning the shared cache entity */ @Override public void collectQueryParameters(Set cacheFields){ cacheFields.addAll(foreignKeyFields); } /** * INTERNAL: * Cascade discover and persist new objects during commit. */ @Override public void cascadeDiscoverAndPersistUnregisteredNewObjects(Object object, Map newObjects, Map unregisteredExistingObjects, Map visitedObjects, UnitOfWorkImpl uow, Set cascadeErrors) { cascadeDiscoverAndPersistUnregisteredNewObjects(object, newObjects, unregisteredExistingObjects, visitedObjects, uow, true, cascadeErrors); } /** * INTERNAL: * Cascade discover and persist new objects during commit. */ public void cascadeDiscoverAndPersistUnregisteredNewObjects(Object object, Map newObjects, Map unregisteredExistingObjects, Map visitedObjects, UnitOfWorkImpl uow, boolean getAttributeValueFromObject, Set cascadeErrors) { Object attributeValue = null; if (getAttributeValueFromObject){ attributeValue = getAttributeValueFromObject(object); } else { attributeValue = object; } if (attributeValue != null && this.indirectionPolicy.objectIsInstantiated(attributeValue)) { if (getAttributeValueFromObject){ attributeValue = this.indirectionPolicy.getRealAttributeValueFromObject(object, attributeValue); } // remove private owned object from uow list if (isCandidateForPrivateOwnedRemoval()) { uow.removePrivateOwnedObject(this, attributeValue); } uow.discoverAndPersistUnregisteredNewObjects(attributeValue, isCascadePersist(), newObjects, unregisteredExistingObjects, visitedObjects, cascadeErrors); } } /** * INTERNAL: * Cascade registerNew for Create through mappings that require the cascade */ @Override public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects){ cascadeRegisterNewIfRequired(object, uow, visitedObjects, true); } /** * INTERNAL: * Cascade registerNew for Create through mappings that require the cascade * @param object is either the source object, or attribute value if getAttributeValueFromObject is true. */ public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects, boolean getAttributeValueFromObject) { if (!isCascadePersist()) { return; } Object attributeValue = null; if (getAttributeValueFromObject) { attributeValue = getAttributeValueFromObject(object); } else { attributeValue = object; } if ((attributeValue != null) // no need to check for new as persist must be cascaded. && (this.indirectionPolicy.objectIsInstantiated(attributeValue) || uow.isCloneNewObject(object))) { if (getAttributeValueFromObject){ attributeValue = this.indirectionPolicy.getRealAttributeValueFromObject(object, attributeValue); } uow.registerNewObjectForPersist(attributeValue, visitedObjects); // add private owned object to uow list if mapping is a candidate and uow should discover new objects and the source object is new. if (isCandidateForPrivateOwnedRemoval() && uow.shouldDiscoverNewObjects() && (attributeValue != null) && uow.isCloneNewObject(object)) { uow.addPrivateOwnedObject(this, attributeValue); } } } /** * INTERNAL: */ protected Object getPrimaryKeyForObject(Object object, AbstractSession session) { return getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(object, session); } /** * INTERNAL: * The returns if the mapping has any constraint dependencies, such as foreign keys and join tables. */ @Override public boolean hasConstraintDependency() { return isForeignKeyRelationship(); } /** * INTERNAL: * Builder the unit of work value holder. * @param buildDirectlyFromRow indicates that we are building the clone directly * from a row as opposed to building the original from the row, putting it in * the shared cache, and then cloning the original. */ @Override public DatabaseValueHolder createCloneValueHolder(ValueHolderInterface attributeValue, Object original, Object clone, AbstractRecord row, AbstractSession cloningSession, boolean buildDirectlyFromRow) { DatabaseValueHolder valueHolder = null; //Bug#457480 : If original (from cache) is null, load from row if ((row == null && original != null) && (isPrimaryKeyMapping())) { // The row must be built if a primary key mapping for remote case. AbstractRecord rowFromTargetObject = extractPrimaryKeyRowForSourceObject(original, cloningSession); valueHolder = cloningSession.createCloneQueryValueHolder(attributeValue, clone, rowFromTargetObject, this); } else { valueHolder = cloningSession.createCloneQueryValueHolder(attributeValue, clone, row, this); } // In case of joined attributes it so happens that the attributeValue // contains a registered clone, as valueFromRow was called with a // UnitOfWork. So switch the values. // Note that this UOW valueholder starts off as instantiated but that // is fine, for the reality is that it is. if (buildDirectlyFromRow && attributeValue.isInstantiated()) { T cloneAttributeValue = attributeValue.getValue(); valueHolder.privilegedSetValue(cloneAttributeValue); valueHolder.setInstantiated(); // PERF: Do not modify the original value-holder, it is never used. } return valueHolder; } /** * INTERNAL: * Extract the reference pk for rvh usage in remote model. */ public AbstractRecord extractPrimaryKeyRowForSourceObject(Object domainObject, AbstractSession session) { AbstractRecord databaseRow = getDescriptor().getObjectBuilder().createRecord(session); writeFromObjectIntoRow(domainObject, databaseRow, session, WriteType.UNDEFINED); return databaseRow; } /** * INTERNAL: * Extract the reference pk for rvh usage in remote model. */ public Object extractPrimaryKeysForReferenceObject(Object domainObject, AbstractSession session) { return this.indirectionPolicy.extractPrimaryKeyForReferenceObject(getAttributeValueFromObject(domainObject), session); } /** * INTERNAL: * Return the primary key for the reference object (i.e. the object * object referenced by domainObject and specified by mapping). * This key will be used by a RemoteValueHolder. */ public Object extractPrimaryKeysForReferenceObjectFromRow(AbstractRecord row) { return null; } /** * INTERNAL: * Extract the reference pk for rvh usage in remote model. */ public Object extractPrimaryKeysFromRealReferenceObject(Object object, AbstractSession session) { if (object == null) { return null; } else { Object implementation = getReferenceDescriptor().getObjectBuilder().unwrapObject(object, session); return getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(implementation, session); } } /** * INTERNAL: * Initialize the state of mapping. */ @Override public void preInitialize(AbstractSession session) throws DescriptorException { super.preInitialize(session); //Bug#4251902 Make Proxy Indirection writable and readable to deployment xml. If ProxyIndirectionPolicy does not //have any targetInterfaces, build a new set. if ((this.indirectionPolicy instanceof ProxyIndirectionPolicy) && !((ProxyIndirectionPolicy)this.indirectionPolicy).hasTargetInterfaces()) { useProxyIndirection(); } } /** * INTERNAL: * Insert privately owned parts */ protected void insert(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { // Checks if privately owned parts should be inserted or not. if (!shouldObjectModifyCascadeToParts(query)) { return; } // Get the privately owned parts Object object = getRealAttributeValueFromObject(query.getObject(), query.getSession()); if (object == null) { return; } AbstractSession session = query.getSession(); // PERF: Avoid query execution if already written. if (session.getCommitManager().isCommitCompletedInPostOrIgnore(object)) { return; } ObjectChangeSet changeSet = null; // Get changeSet for referenced object. Change record may not exist for new objects, so always lookup. if (session.isUnitOfWork() && (((UnitOfWorkImpl)session).getUnitOfWorkChangeSet() != null)) { UnitOfWorkChangeSet uowChangeSet = (UnitOfWorkChangeSet)((UnitOfWorkImpl)session).getUnitOfWorkChangeSet(); changeSet = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(object); // PERF: If the changeSet is null it must be existing, if it is not new, then cascading is not required. if (changeSet == null || !changeSet.isNew()) { return; } } WriteObjectQuery writeQuery = null; // If private owned, the dependent objects should also be new. // However a bug was logged was put in to allow dependent objects to be existing in a unit of work, // so this allows existing dependent objects in the unit of work. if (this.isPrivateOwned && ((changeSet == null) || (changeSet.isNew()))) { // no identity check needed for private owned writeQuery = new InsertObjectQuery(); } else { writeQuery = new WriteObjectQuery(); } writeQuery.setIsExecutionClone(true); writeQuery.setObject(object); writeQuery.setObjectChangeSet(changeSet); writeQuery.setCascadePolicy(query.getCascadePolicy()); session.executeQuery(writeQuery); } /** * INTERNAL: * Update the private owned part. */ protected void update(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { if (!shouldObjectModifyCascadeToParts(query)) { return; } Object sourceObject = query.getObject(); Object attributeValue = getAttributeValueFromObject(sourceObject); // If objects are not instantiated that means they are not changed. if (!this.indirectionPolicy.objectIsInstantiated(attributeValue)) { return; } // Get the privately owned parts in the memory AbstractSession session = query.getSession(); Object object = getRealAttributeValueFromAttribute(attributeValue, sourceObject, session); if (object != null) { ObjectChangeSet changeSet = query.getObjectChangeSet(); if (changeSet != null) { ObjectReferenceChangeRecord changeRecord = (ObjectReferenceChangeRecord)query.getObjectChangeSet().getChangesForAttributeNamed(getAttributeName()); if (changeRecord != null) { changeSet = (ObjectChangeSet)changeRecord.getNewValue(); // PERF: If it is not new, then cascading is not required. if (!changeSet.isNew()) { return; } } else { // no changeRecord no change to reference. return; } } else { UnitOfWorkChangeSet uowChangeSet = null; // Get changeSet for referenced object. if (session.isUnitOfWork() && (((UnitOfWorkImpl)session).getUnitOfWorkChangeSet() != null)) { uowChangeSet = (UnitOfWorkChangeSet)((UnitOfWorkImpl)session).getUnitOfWorkChangeSet(); changeSet = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(object); // PERF: If the changeSet is null it must be existing, if it is not new, then cascading is not required. if (changeSet == null || !changeSet.isNew()) { return; } } } // PERF: Only write dependent object if they are new. if ((!query.shouldCascadeOnlyDependentParts()) || (changeSet == null) || changeSet.isNew()) { // PERF: Avoid query execution if already written. if (session.getCommitManager().isCommitCompletedInPostOrIgnore(object)) { return; } WriteObjectQuery writeQuery = new WriteObjectQuery(); writeQuery.setIsExecutionClone(true); writeQuery.setObject(object); writeQuery.setObjectChangeSet(changeSet); writeQuery.setCascadePolicy(query.getCascadePolicy()); session.executeQuery(writeQuery); } } } /** * PUBLIC: * Indicates whether the mapping has RelationTableMechanism. */ public boolean hasRelationTableMechanism() { return false; } /** * PUBLIC: * Set this mapping to use Proxy Indirection. *

* Proxy Indirection uses the Proxy and InvocationHandler features * of JDK 1.3 to provide "transparent indirection" for 1:1 relationships. In order to use Proxy * Indirection: * *

    *
  • The target class must implement at least one public interface *
  • The attribute on the source class must be typed as that public interface *
  • get() and set() methods for the attribute must use the interface *
* * With this policy, proxy objects are returned during object creation. When a message other than * toString is called on the proxy the real object data is retrieved from the database. *

* By default, use the target class' full list of interfaces for the proxy. * */ public void useProxyIndirection() { Class[] targetInterfaces = getReferenceClass().getInterfaces(); if (!getReferenceClass().isInterface() && getReferenceClass().getSuperclass() == null) { setIndirectionPolicy(new ProxyIndirectionPolicy(targetInterfaces)); } else { HashSet targetInterfacesCol = new HashSet(); //Bug#4432781 Include all the interfaces and the super interfaces of the target class if (getReferenceClass().getSuperclass() != null) { buildTargetInterfaces(getReferenceClass(), targetInterfacesCol); } //Bug#4251902 Make Proxy Indirection writable and readable to deployment xml. If //ReferenceClass is an interface, it needs to be included in the array. if (getReferenceClass().isInterface()) { targetInterfacesCol.add(getReferenceClass()); } targetInterfaces = (Class[])targetInterfacesCol.toArray(targetInterfaces); setIndirectionPolicy(new ProxyIndirectionPolicy(targetInterfaces)); } } /** * INTERNAL: This method will access the target relationship and create a * list of PKs of the target entities. This method is used in combination * with the CachedValueHolder to store references to PK's to be loaded from * a cache instead of a query. * @see org.eclipse.persistence.internal.queries.ContainerPolicy#buildReferencesPKList * @see org.eclipse.persistence.internal.queries.MappedKeyMapContainerPolicy */ @Override public Object[] buildReferencesPKList(Object entity, Object attribute, AbstractSession session) { ClassDescriptor referenceDescriptor = getReferenceDescriptor(); Object target = this.indirectionPolicy.getRealAttributeValueFromObject(entity, attribute); if (target != null){ Object[] result = new Object[1]; result[0] = referenceDescriptor.getObjectBuilder().extractPrimaryKeyFromObject(target, session); return result; } return new Object[]{}; } /** * INTERNAL: * Build a list of all the interfaces and super interfaces for a given class. */ public Collection buildTargetInterfaces(Class aClass, Collection targetInterfacesCol) { Class[] targetInterfaces = aClass.getInterfaces(); targetInterfacesCol.addAll(Arrays.asList(targetInterfaces)); if (aClass.getSuperclass() == null) { return targetInterfacesCol; } else { return buildTargetInterfaces(aClass.getSuperclass(), targetInterfacesCol); } } /** * PUBLIC: * Set this mapping to use Proxy Indirection. *

* Proxy Indirection uses the Proxy and InvocationHandler features * of JDK 1.3 to provide "transparent indirection" for 1:1 relationships. In order to use Proxy * Indirection: * *

    *
  • The target class must implement at least one public interface *
  • The attribute on the source class must be typed as that public interface *
  • get() and set() methods for the attribute must use the interface *
* * With this policy, proxy objects are returned during object creation. When a message other than * toString is called on the proxy the real object data is retrieved from the database. * * @param targetInterfaces The interfaces that the target class implements. The attribute must be typed * as one of these interfaces. */ public void useProxyIndirection(Class[] targetInterfaces) { setIndirectionPolicy(new ProxyIndirectionPolicy(targetInterfaces)); } /** * PUBLIC: * Set this mapping to use Proxy Indirection. *

* Proxy Indirection uses the Proxy and InvocationHandler features * of JDK 1.3 to provide "transparent indirection" for 1:1 relationships. In order to use Proxy * Indirection: * *

    *
  • The target class must implement at least one public interface *
  • The attribute on the source class must be typed as that public interface *
  • get() and set() methods for the attribute must use the interface *
* * With this policy, proxy objects are returned during object creation. When a message other than * toString is called on the proxy the real object data is retrieved from the database. * * @param targetInterface The interface that the target class implements. The attribute must be typed * as this interface. */ public void useProxyIndirection(Class targetInterface) { Class[] targetInterfaces = new Class[] { targetInterface }; setIndirectionPolicy(new ProxyIndirectionPolicy(targetInterfaces)); } /** * INTERNAL: * This method is used to load a relationship from a list of PKs. * This list may be available if the relationship has been cached. */ @Override public Object valueFromPKList(Object[] pks, AbstractRecord foreignKeys, AbstractSession session) { if (pks.length == 0 || pks[0] == null) return null; ReadObjectQuery query = new ReadObjectQuery(); query.setReferenceClass(getReferenceClass()); query.setSelectionId(pks[0]); query.setIsExecutionClone(true); query.setSession(session); return session.executeQuery(query); } /** * INTERNAL: * To verify if the specified object is deleted or not. */ @Override public boolean verifyDelete(Object object, AbstractSession session) throws DatabaseException { if (isPrivateOwned() || isCascadeRemove()) { Object attributeValue = getRealAttributeValueFromObject(object, session); if (attributeValue != null) { return session.verifyDelete(attributeValue); } } return true; } /** * INTERNAL: * Get a value from the object and set that in the respective field of the row. * But before that check if the reference object is instantiated or not. */ @Override public void writeFromObjectIntoRowForUpdate(WriteObjectQuery query, AbstractRecord databaseRow) { Object object = query.getObject(); AbstractSession session = query.getSession(); if (!isAttributeValueInstantiated(object)) { return; } if (session.isUnitOfWork()) { if (compareObjectsWithoutPrivateOwned(query.getBackupClone(), object, session)) { return; } } writeFromObjectIntoRow(object, databaseRow, session, WriteType.UPDATE); } /** * INTERNAL: * Get a value from the object and set that in the respective field of the row. */ @Override public void writeFromObjectIntoRowForWhereClause(ObjectLevelModifyQuery query, AbstractRecord databaseRow) { if (isReadOnly()) { return; } if (query.isDeleteObjectQuery()) { writeFromObjectIntoRow(query.getObject(), databaseRow, query.getSession(), WriteType.UNDEFINED); } else { // If the original was never instantiated the backup clone has a ValueHolder of null // so for this case we must extract from the original object. if (isAttributeValueInstantiated(query.getObject())) { writeFromObjectIntoRow(query.getBackupClone(), databaseRow, query.getSession(), WriteType.UNDEFINED); } else { writeFromObjectIntoRow(query.getObject(), databaseRow, query.getSession(), WriteType.UNDEFINED); } } } /** * INTERNAL: * Return if this mapping supports change tracking. */ @Override public boolean isChangeTrackingSupported(Project project) { return true; } /** * INTERNAL: * Either create a new change record or update the change record with the new value. * This is used by attribute change tracking. */ @Override public void updateChangeRecord(Object clone, Object newValue, Object oldValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) { // Must ensure values are unwrapped. Object unwrappedNewValue = newValue; Object unwrappedOldValue = oldValue; if (newValue != null) { unwrappedNewValue = getReferenceDescriptor().getObjectBuilder().unwrapObject(newValue, uow); } if (oldValue != null) { unwrappedOldValue = getReferenceDescriptor().getObjectBuilder().unwrapObject(oldValue, uow); } ObjectReferenceChangeRecord changeRecord = (ObjectReferenceChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName()); if (changeRecord == null) { changeRecord = internalBuildChangeRecord(unwrappedNewValue, objectChangeSet, uow); changeRecord.setOldValue(unwrappedOldValue); objectChangeSet.addChange(changeRecord); } else { setNewValueInChangeRecord(unwrappedNewValue, changeRecord, objectChangeSet, uow); } } /** * INTERNAL: * Update a ChangeRecord to replace the ChangeSet for the old entity with the changeSet for the new Entity. This is * used when an Entity is merged into itself and the Entity reference new or detached entities. */ @Override public void updateChangeRecordForSelfMerge(ChangeRecord changeRecord, Object source, Object target, UnitOfWorkChangeSet parentUOWChangeSet, UnitOfWorkImpl unitOfWork){ ((ObjectReferenceChangeRecord)changeRecord).setNewValue(((UnitOfWorkChangeSet)unitOfWork.getUnitOfWorkChangeSet()).findOrCreateLocalObjectChangeSet(target, referenceDescriptor, unitOfWork.isCloneNewObject(target))); } /** * INTERNAL: * Directly build a change record without comparison */ @Override public ChangeRecord buildChangeRecord(Object clone, ObjectChangeSet owner, AbstractSession session) { return internalBuildChangeRecord(getRealAttributeValueFromObject(clone, session), owner, session); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy