blob: a575fbf69b3e00ab91fa77897ba037f6625027e2 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
// 01/15/2015-2.6 Mythily Parthasarathy
// - 457480: NPE in MethodAttributeAccessor.getAttributeValueFromObject
package org.eclipse.persistence.mappings;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
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;
/**
* <p><b>Purpose</b>: 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 Vector<DatabaseField> 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 ){
switch(refreshCascade){
case ObjectBuildingQuery.CascadeAllParts :
return unitOfWork.mergeClone(attributeValue, MergeManager.CASCADE_ALL_PARTS, true);
case ObjectBuildingQuery.CascadePrivateParts :
return unitOfWork.mergeClone(attributeValue, MergeManager.CASCADE_PRIVATE_PARTS, true);
case ObjectBuildingQuery.CascadeByMapping :
return unitOfWork.mergeClone(attributeValue, MergeManager.CASCADE_BY_MAPPING, true);
default:
return 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<Object, ObjectDescriptor> objectDescriptors, Map<Object, Object> 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 Vector<DatabaseField> 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 Vector<DatabaseField> 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(Vector<DatabaseField> 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<DatabaseField> cacheFields){
for (DatabaseField field : foreignKeyFields) {
cacheFields.add(field);
}
}
/**
* 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 <T> DatabaseValueHolder<T> createCloneValueHolder(ValueHolderInterface<T> attributeValue, Object original, Object clone, AbstractRecord row, AbstractSession cloningSession, boolean buildDirectlyFromRow) {
DatabaseValueHolder<T> 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 <CODE>Proxy</CODE> and <CODE>InvocationHandler</CODE> features
* of JDK 1.3 to provide "transparent indirection" for 1:1 relationships. In order to use Proxy
* Indirection:
*
* <UL>
* <LI>The target class must implement at least one public interface
* <LI>The attribute on the source class must be typed as that public interface
* <LI>get() and set() methods for the attribute must use the interface
* </UL>
*
* With this policy, proxy objects are returned during object creation. When a message other than
* <CODE>toString</CODE> 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();
for (int index = 0; index < targetInterfaces.length; index++) {
targetInterfacesCol.add(targetInterfaces[index]);
}
if (aClass.getSuperclass() == null) {
return targetInterfacesCol;
} else {
return buildTargetInterfaces(aClass.getSuperclass(), targetInterfacesCol);
}
}
/**
* PUBLIC:
* Set this mapping to use Proxy Indirection.
*
* Proxy Indirection uses the <CODE>Proxy</CODE> and <CODE>InvocationHandler</CODE> features
* of JDK 1.3 to provide "transparent indirection" for 1:1 relationships. In order to use Proxy
* Indirection:
*
* <UL>
* <LI>The target class must implement at least one public interface
* <LI>The attribute on the source class must be typed as that public interface
* <LI>get() and set() methods for the attribute must use the interface
* </UL>
*
* With this policy, proxy objects are returned during object creation. When a message other than
* <CODE>toString</CODE> 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 <CODE>Proxy</CODE> and <CODE>InvocationHandler</CODE> features
* of JDK 1.3 to provide "transparent indirection" for 1:1 relationships. In order to use Proxy
* Indirection:
*
* <UL>
* <LI>The target class must implement at least one public interface
* <LI>The attribute on the source class must be typed as that public interface
* <LI>get() and set() methods for the attribute must use the interface
* </UL>
*
* With this policy, proxy objects are returned during object creation. When a message other than
* <CODE>toString</CODE> 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);
}
}