| /* |
| * Copyright (c) 1998, 2019 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.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 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 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 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()) { |
| Object 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); |
| } |
| } |