| /* |
| * 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 |
| // 07/19/2011-2.2.1 Guy Pelletier |
| // - 338812: ManyToMany mapping in aggregate object violate integrity constraint on deletion |
| // // 30/05/2012-2.4 Guy Pelletier |
| // - 354678: Temp classloader is still being used during metadata processing |
| package org.eclipse.persistence.mappings; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.persistence.descriptors.changetracking.ChangeTracker; |
| import org.eclipse.persistence.descriptors.changetracking.CollectionChangeEvent; |
| import org.eclipse.persistence.descriptors.changetracking.MapChangeEvent; |
| import org.eclipse.persistence.exceptions.DatabaseException; |
| import org.eclipse.persistence.exceptions.DescriptorException; |
| import org.eclipse.persistence.exceptions.ValidationException; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.expressions.ExpressionBuilder; |
| import org.eclipse.persistence.indirection.IndirectCollection; |
| import org.eclipse.persistence.indirection.ValueHolder; |
| import org.eclipse.persistence.internal.descriptors.DescriptorIterator; |
| import org.eclipse.persistence.internal.descriptors.ObjectBuilder; |
| import org.eclipse.persistence.internal.descriptors.changetracking.AttributeChangeListener; |
| import org.eclipse.persistence.internal.descriptors.changetracking.ObjectChangeListener; |
| import org.eclipse.persistence.internal.expressions.SQLDeleteStatement; |
| import org.eclipse.persistence.internal.expressions.SQLSelectStatement; |
| import org.eclipse.persistence.internal.helper.ClassConstants; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.indirection.TransparentIndirectionPolicy; |
| import org.eclipse.persistence.internal.queries.ContainerPolicy; |
| import org.eclipse.persistence.internal.queries.JoinedAttributeManager; |
| import org.eclipse.persistence.internal.queries.MappedKeyMapContainerPolicy; |
| 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.DirectMapChangeRecord; |
| import org.eclipse.persistence.internal.sessions.MergeManager; |
| import org.eclipse.persistence.internal.sessions.ObjectChangeSet; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.mappings.converters.Converter; |
| import org.eclipse.persistence.mappings.converters.ObjectTypeConverter; |
| import org.eclipse.persistence.mappings.converters.SerializedObjectConverter; |
| import org.eclipse.persistence.mappings.converters.TypeConversionConverter; |
| import org.eclipse.persistence.mappings.foundation.MapComponentMapping; |
| import org.eclipse.persistence.queries.DataReadQuery; |
| import org.eclipse.persistence.queries.DatabaseQuery; |
| import org.eclipse.persistence.queries.DeleteObjectQuery; |
| import org.eclipse.persistence.queries.DirectReadQuery; |
| import org.eclipse.persistence.queries.ObjectBuildingQuery; |
| import org.eclipse.persistence.queries.ReadAllQuery; |
| import org.eclipse.persistence.queries.WriteObjectQuery; |
| import org.eclipse.persistence.sessions.DatabaseRecord; |
| |
| /** |
| * Mapping for a collection of key-value pairs. |
| * The key and value must be simple types (String, Number, Date, etc.) |
| * and stored in a single table along with a foreign key to the source object. |
| * A converter can be used on the key and value if the desired object types |
| * do not match the data types. |
| * |
| * @see Converter |
| * @see ObjectTypeConverter |
| * @see TypeConversionConverter |
| * @see SerializedObjectConverter |
| * |
| * @author Steven Vo |
| * @since TopLink 3.5 |
| */ |
| public class DirectMapMapping extends DirectCollectionMapping implements MapComponentMapping { |
| |
| /** |
| * DirectMapCollectionMapping constructor |
| */ |
| public DirectMapMapping() { |
| super(); |
| DataReadQuery query = new DataReadQuery(); |
| this.selectionQuery = query; |
| MappedKeyMapContainerPolicy mapPolicy = new MappedKeyMapContainerPolicy(ClassConstants.Hashtable_Class); |
| mapPolicy.setValueMapping(this); |
| this.containerPolicy = mapPolicy; |
| this.isListOrderFieldSupported = false; |
| } |
| |
| /** |
| * ADVANCED: |
| * Configure the mapping to use a container policy. |
| * This must be a MappedKeyMapContainerPolicy policy. |
| * Set the valueMapping for the policy. |
| */ |
| @Override |
| public void setContainerPolicy(ContainerPolicy containerPolicy) { |
| super.setContainerPolicy(containerPolicy); |
| ((MappedKeyMapContainerPolicy)containerPolicy).setValueMapping(this); |
| } |
| |
| private MappedKeyMapContainerPolicy getMappedKeyMapContainerPolicy(){ |
| return (MappedKeyMapContainerPolicy)containerPolicy; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the converter on the mapping. |
| * A converter can be used to convert between the key's object value and database value. |
| */ |
| public Converter getKeyConverter() { |
| return getMappedKeyMapContainerPolicy().getKeyConverter(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the converter on the mapping. |
| * A converter can be used to convert between the key's object value and database value. |
| */ |
| public void setKeyConverter(Converter keyConverter) { |
| getMappedKeyMapContainerPolicy().setKeyConverter(keyConverter, this); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the converter class name on the mapping. Initialized in |
| * convertClassNamesToClasses. |
| * A converter can be used to convert between the key's object value and database value. |
| */ |
| public void setKeyConverterClassName(String keyConverterClassName) { |
| getMappedKeyMapContainerPolicy().setKeyConverterClassName(keyConverterClassName, this); |
| } |
| |
| /** |
| * INTERNAL: |
| * Add a new value and its change set to the collection change record. This is used by |
| * attribute change tracking. If a value has changed then issue a remove first with the key |
| * then an add. |
| */ |
| public void addToCollectionChangeRecord(Object newKey, Object newValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) throws DescriptorException { |
| DirectMapChangeRecord collectionChangeRecord = (DirectMapChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName()); |
| if (collectionChangeRecord == null) { |
| collectionChangeRecord = new DirectMapChangeRecord(objectChangeSet); |
| collectionChangeRecord.setAttribute(getAttributeName()); |
| collectionChangeRecord.setMapping(this); |
| objectChangeSet.addChange(collectionChangeRecord); |
| } |
| collectionChangeRecord.addAdditionChange(newKey, newValue); |
| } |
| |
| /** |
| * 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 containerPolicy.containerInstance(1); |
| } |
| Object clonedAttributeValue = containerPolicy.containerInstance(containerPolicy.sizeFor(attributeValue)); |
| |
| // I need to synchronize here to prevent the collection from changing while I am cloning it. |
| // This will occur when I am merging into the cache and I am instantiating a UOW valueHolder at the same time |
| // I can not synchronize around the clone, as this will cause deadlocks, so I will need to copy the collection then create the clones |
| // I will use a temporary collection to help speed up the process |
| Object temporaryCollection = null; |
| synchronized (attributeValue) { |
| temporaryCollection = containerPolicy.cloneFor(attributeValue); |
| } |
| |
| for (Object keysIterator = containerPolicy.iteratorFor(temporaryCollection); |
| containerPolicy.hasNext(keysIterator);) { |
| Map.Entry entry = (Map.Entry)containerPolicy.nextEntry(keysIterator, cloningSession); |
| Object cloneKey = containerPolicy.buildCloneForKey(entry.getKey(), clone, cacheKey, null, cloningSession, isExisting, isFromSharedCache); |
| Object cloneValue = buildElementClone(entry.getValue(), clone, cacheKey, refreshCascade, cloningSession, isExisting, isFromSharedCache); |
| containerPolicy.addInto(cloneKey, cloneValue, clonedAttributeValue, cloningSession); |
| } |
| return clonedAttributeValue; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used by AttributeLevelChangeTracking to update a changeRecord with calculated changes |
| * as opposed to detected changes. If an attribute can not be change tracked it's |
| * changes can be detected through this process. |
| */ |
| @Override |
| public void calculateDeferredChanges(ChangeRecord changeRecord, AbstractSession session) { |
| DirectMapChangeRecord collectionRecord = (DirectMapChangeRecord)changeRecord; |
| // TODO: Handle events that fired after collection was replaced. |
| compareCollectionsForChange(collectionRecord.getOriginalCollection(), collectionRecord.getLatestCollection(), collectionRecord, session); |
| } |
| |
| |
| /** |
| * 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) { |
| if (containerPolicy.isMappedKeyMapPolicy()){ |
| Object values = getAttributeValueFromObject(object); |
| if (values != null){ |
| Object iterator = containerPolicy.iteratorFor(values); |
| while (containerPolicy.hasNext(iterator)){ |
| Object wrappedObject = containerPolicy.nextEntry(iterator, uow); |
| containerPolicy.cascadeDiscoverAndPersistUnregisteredNewObjects(wrappedObject, newObjects, unregisteredExistingObjects, visitedObjects, uow, cascadeErrors); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade perform delete through mappings that require the cascade |
| */ |
| @Override |
| public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { |
| if (containerPolicy.isMappedKeyMapPolicy()){ |
| Object values = getAttributeValueFromObject(object); |
| if (values != null){ |
| Object iterator = containerPolicy.iteratorFor(values); |
| while (containerPolicy.hasNext(iterator)){ |
| Object wrappedObject = containerPolicy.nextEntry(iterator, uow); |
| containerPolicy.cascadePerformRemoveIfRequired(wrappedObject, uow, visitedObjects); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade registerNew for Create through mappings that require the cascade |
| */ |
| @Override |
| public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { |
| if (containerPolicy.isMappedKeyMapPolicy()){ |
| Object values = getAttributeValueFromObject(object); |
| if (values != null){ |
| Object iterator = containerPolicy.iteratorFor(values); |
| while (containerPolicy.hasNext(iterator)){ |
| Object wrappedObject = containerPolicy.nextEntry(iterator, uow); |
| containerPolicy.cascadeRegisterNewIfRequired(wrappedObject, uow, visitedObjects); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to calculate the differences between two collections. |
| */ |
| @Override |
| public void compareCollectionsForChange(Object oldCollection, Object newCollection, ChangeRecord changeRecord, AbstractSession session) { |
| HashMap originalKeyValues = new HashMap(10); |
| HashMap cloneKeyValues = new HashMap(10); |
| |
| if (oldCollection != null) { |
| Map backUpCollection = (Map)oldCollection; |
| Object backUpIter = containerPolicy.iteratorFor(backUpCollection); |
| while (containerPolicy.hasNext(backUpIter)) {// Make a lookup of the objects |
| Map.Entry entry = (Map.Entry)containerPolicy.nextEntry(backUpIter, session); |
| originalKeyValues.put(entry.getKey(), backUpCollection.get(entry.getKey())); |
| } |
| } |
| |
| Map cloneObjectCollection = (Map)newCollection; |
| Object cloneIter = containerPolicy.iteratorFor(cloneObjectCollection); |
| while (containerPolicy.hasNext(cloneIter)) {//Compare them with the objects from the clone |
| Map.Entry wrappedFirstObject = (Map.Entry)containerPolicy.nextEntry(cloneIter, session); |
| Object firstValue = wrappedFirstObject.getValue(); |
| Object firstKey = wrappedFirstObject.getKey(); |
| Object backupValue = originalKeyValues.get(firstKey); |
| if (!originalKeyValues.containsKey(firstKey)) { |
| cloneKeyValues.put(firstKey, cloneObjectCollection.get(firstKey)); |
| } else if (((backupValue == null) && (firstValue != null)) || (!backupValue.equals(firstValue))) {//the object was not in the backup |
| cloneKeyValues.put(firstKey, cloneObjectCollection.get(firstKey)); |
| } else { |
| originalKeyValues.remove(firstKey); |
| } |
| } |
| |
| ((DirectMapChangeRecord)changeRecord).clearChanges(); |
| ((DirectMapChangeRecord)changeRecord).addAdditionChange(cloneKeyValues); |
| ((DirectMapChangeRecord)changeRecord).addRemoveChange(originalKeyValues); |
| ((DirectMapChangeRecord)changeRecord).setIsDeferred(false); |
| ((DirectMapChangeRecord)changeRecord).setLatestCollection(null); |
| } |
| |
| /** |
| * INTERNAL: |
| * This method compares the changes between two direct collections. Comparisons are made on equality |
| * not identity. |
| */ |
| @Override |
| public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession session) { |
| Object cloneAttribute = null; |
| Object backUpAttribute = null; |
| |
| cloneAttribute = getAttributeValueFromObject(clone); |
| if ((cloneAttribute != null) && (!getIndirectionPolicy().objectIsInstantiated(cloneAttribute))) { |
| return null; |
| } |
| |
| Map cloneObjectCollection = (Map)getRealCollectionAttributeValueFromObject(clone, session); |
| |
| Map backUpCollection = null; |
| |
| if (!owner.isNew()) { |
| backUpAttribute = getAttributeValueFromObject(backUp); |
| if ((backUpAttribute == null) && (cloneAttribute == null)) { |
| return null; |
| } |
| backUpCollection = (Map)getRealCollectionAttributeValueFromObject(backUp, session); |
| } |
| |
| DirectMapChangeRecord changeRecord = new DirectMapChangeRecord(owner); |
| changeRecord.setAttribute(getAttributeName()); |
| changeRecord.setMapping(this); |
| compareCollectionsForChange(backUpCollection, cloneObjectCollection, changeRecord, session); |
| if (changeRecord.hasChanges()) { |
| changeRecord.setOriginalCollection(backUpCollection); |
| return changeRecord; |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the attributes belonging to this mapping for the objects. |
| */ |
| @Override |
| public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) { |
| Object firstObjectMap = getRealCollectionAttributeValueFromObject(firstObject, session); |
| Object secondObjectMap = getRealCollectionAttributeValueFromObject(secondObject, session); |
| return getMappedKeyMapContainerPolicy().compareContainers(firstObjectMap, secondObjectMap); |
| } |
| |
| /* |
| * INTERNAL: |
| * Convert all the class-name-based settings in this mapping to actual |
| * class-based settings. This method is implemented by subclasses as |
| * necessary. |
| * @param classLoader |
| */ |
| @Override |
| public void convertClassNamesToClasses(ClassLoader classLoader) { |
| super.convertClassNamesToClasses(classLoader); |
| |
| if (getDirectKeyField() != null) { |
| getDirectKeyField().convertClassNamesToClasses(classLoader); |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Called when a DatabaseMapping is used to map the key in a collection. Returns the key. |
| */ |
| @Override |
| public Object createMapComponentFromRow(AbstractRecord dbRow, ObjectBuildingQuery query, CacheKey parentCacheKey, AbstractSession session, boolean isTargetProtected){ |
| Object key = dbRow.get(getDirectField()); |
| if (getValueConverter() != null){ |
| key = getValueConverter().convertDataValueToObjectValue(key, session); |
| } |
| return key; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| public DatabaseField getDirectKeyField() { |
| return getMappedKeyMapContainerPolicy().getDirectKeyField(null); |
| } |
| |
| /** |
| * INTERNAL: |
| * Initialize and validate the mapping properties. |
| */ |
| @Override |
| public void initialize(AbstractSession session) throws DescriptorException { |
| getMappedKeyMapContainerPolicy().setDescriptorForKeyMapping(this.getDescriptor()); |
| if (getKeyConverter() != null) { |
| getKeyConverter().initialize(this, session); |
| } |
| super.initialize(session); |
| } |
| |
| @Override |
| protected void initializeDeleteQuery(AbstractSession session) { |
| if (!getDeleteQuery().hasSessionName()) { |
| getDeleteQuery().setSessionName(session.getName()); |
| } |
| |
| if (hasCustomDeleteQuery()) { |
| return; |
| } |
| |
| Expression builder = new ExpressionBuilder(); |
| Expression directKeyExp = null; |
| List<DatabaseField> identityFields = getContainerPolicy().getIdentityFieldsForMapKey(); |
| Iterator<DatabaseField> i = identityFields.iterator(); |
| while (i.hasNext()){ |
| DatabaseField field = i.next(); |
| Expression fieldExpression = builder.getField(field).equal(builder.getParameter(field)); |
| if (directKeyExp == null){ |
| directKeyExp = fieldExpression; |
| } else { |
| directKeyExp = directKeyExp.and(fieldExpression); |
| } |
| } |
| Expression expression = null; |
| SQLDeleteStatement statement = new SQLDeleteStatement(); |
| |
| // Construct an expression to delete from the relation table. |
| for (int index = 0; index < getReferenceKeyFields().size(); index++) { |
| DatabaseField referenceKey = getReferenceKeyFields().get(index); |
| DatabaseField sourceKey = getSourceKeyFields().get(index); |
| |
| Expression subExp1 = builder.getField(referenceKey); |
| Expression subExp2 = builder.getParameter(sourceKey); |
| Expression subExpression = subExp1.equal(subExp2); |
| |
| expression = subExpression.and(expression); |
| } |
| expression = expression.and(directKeyExp); |
| statement.setWhereClause(expression); |
| statement.setTable(getReferenceTable()); |
| getDeleteQuery().setSQLStatement(statement); |
| } |
| |
| /** |
| * Initialize insert query. This query is used to insert the collection of objects into the |
| * reference table. |
| */ |
| @Override |
| protected void initializeInsertQuery(AbstractSession session) { |
| super.initializeInsertQuery(session); |
| getContainerPolicy().addFieldsForMapKey(getInsertQuery().getModifyRow()); |
| } |
| |
| @Override |
| protected void initializeSelectionStatement(AbstractSession session) { |
| if (this.selectionQuery.isReadAllQuery()){ |
| ((ReadAllQuery)this.selectionQuery).addAdditionalField(getDirectField().clone()); |
| } else { |
| SQLSelectStatement statement = (SQLSelectStatement)this.selectionQuery.getSQLStatement(); |
| statement.addTable(getReferenceTable()); |
| statement.addField(getDirectField().clone()); |
| getContainerPolicy().addAdditionalFieldsToQuery(this.selectionQuery, getAdditionalFieldsBaseExpression(this.selectionQuery)); |
| statement.normalize(session, null); |
| } |
| if (this.selectionQuery.isDirectReadQuery()){ |
| ((DirectReadQuery)this.selectionQuery).setResultType(DataReadQuery.MAP); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Iterate on the attribute value. |
| * The value holder has already been processed. |
| * PERF: Avoid iteration if not required. |
| */ |
| @Override |
| public void iterateOnRealAttributeValue(DescriptorIterator iterator, Object realAttributeValue) { |
| super.iterateOnRealAttributeValue(iterator, realAttributeValue); |
| ContainerPolicy cp = getContainerPolicy(); |
| if (realAttributeValue != null && !iterator.shouldIterateOnPrimitives()) { |
| for (Object iter = cp.iteratorFor(realAttributeValue); cp.hasNext(iter);) { |
| Object wrappedObject = cp.nextEntry(iter, iterator.getSession()); |
| cp.iterateOnMapKey(iterator, wrappedObject); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Iterate on the specified element. |
| */ |
| @Override |
| public void iterateOnElement(DescriptorIterator iterator, Object element) { |
| super.iterateOnElement(iterator, element); |
| ContainerPolicy cp = getContainerPolicy(); |
| for (Object iter = cp.iteratorFor(element); cp.hasNext(iter);) { |
| Object wrappedObject = cp.nextEntry(iter, iterator.getSession()); |
| cp.iterateOnMapKey(iterator, wrappedObject); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Related mapping should implement this method to return true. |
| */ |
| @Override |
| public boolean isDirectMapMapping() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes from the source to the target object. |
| * Because this is a collection mapping, values are added to or removed from the |
| * collection based on the changeset. |
| */ |
| @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; |
| } |
| Map valueOfTarget = null; |
| AbstractSession session = mergeManager.getSession(); |
| |
| //collect the changes into a vector |
| HashMap addObjects = ((DirectMapChangeRecord)changeRecord).getAddObjects(); |
| HashMap removeObjects = ((DirectMapChangeRecord)changeRecord).getRemoveObjects(); |
| |
| //Check to see if the target has an instantiated collection |
| if ((isAttributeValueInstantiated(target)) && (!changeRecord.getOwner().isNew())) { |
| valueOfTarget = (Map)getRealCollectionAttributeValueFromObject(target, session); |
| } else { |
| //if not create an instance of the map |
| valueOfTarget = (Map)containerPolicy.containerInstance(addObjects.size()); |
| } |
| |
| if (!isAttributeValueInstantiated(target)) { |
| if (mergeManager.shouldMergeChangesIntoDistributedCache()) { |
| return; |
| } |
| |
| Object valueOfSource = getRealCollectionAttributeValueFromObject(source, session); |
| for (Object iterator = containerPolicy.iteratorFor(valueOfSource); |
| containerPolicy.hasNext(iterator);) { |
| Map.Entry entry = (Map.Entry)containerPolicy.nextEntry(iterator, session); |
| containerPolicy.addInto(entry.getKey(), entry.getValue(), valueOfTarget, session); |
| } |
| } else { |
| Object synchronizationTarget = valueOfTarget; |
| // For indirect containers the delegate must be synchronized on, |
| // not the wrapper as the clone synchs on the delegate, see bug#5685287. |
| if (valueOfTarget instanceof IndirectCollection) { |
| synchronizationTarget = ((IndirectCollection)valueOfTarget).getDelegateObject(); |
| } |
| synchronized (synchronizationTarget) { |
| // Next iterate over the changes and add them to the container |
| for (Iterator i = removeObjects.keySet().iterator(); i.hasNext();) { |
| Object keyToRemove = i.next(); |
| containerPolicy.removeFrom(keyToRemove, null, valueOfTarget, session); |
| } |
| |
| for (Iterator i = addObjects.keySet().iterator(); i.hasNext();) { |
| Object keyToAdd = i.next(); |
| Object nextItem = addObjects.get(keyToAdd); |
| if (mergeManager.shouldMergeChangesIntoDistributedCache()) { |
| //bug#4458089 and 4454532- check if collection contains new item before adding during merge into distributed cache |
| if (!containerPolicy.contains(nextItem, valueOfTarget, session)) { |
| containerPolicy.addInto(keyToAdd, nextItem, valueOfTarget, session); |
| } |
| } else { |
| containerPolicy.addInto(keyToAdd, nextItem, valueOfTarget, session); |
| } |
| } |
| } |
| } |
| setRealAttributeValueInObject(target, valueOfTarget); |
| } |
| |
| /** |
| * 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 |
| if (mergeManager.shouldMergeWorkingCopyIntoOriginal() && (!isAttributeValueInstantiated(source))) { |
| setAttributeValueInObject(target, getIndirectionPolicy().getOriginalIndirectionObject(getAttributeValueFromObject(source), targetSession)); |
| return; |
| } |
| } |
| 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 |
| 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; |
| } |
| |
| Map valueOfSource = (Map)getRealCollectionAttributeValueFromObject(source, mergeManager.getSession()); |
| |
| // trigger instantiation of target attribute |
| Object valueOfTarget = getRealCollectionAttributeValueFromObject(target, mergeManager.getSession()); |
| Object newContainer = containerPolicy.containerInstance(containerPolicy.sizeFor(valueOfSource)); |
| |
| boolean fireChangeEvents = false; |
| if ((this.getDescriptor().getObjectChangePolicy().isObjectChangeTrackingPolicy()) && (target instanceof ChangeTracker) && (((ChangeTracker)target)._persistence_getPropertyChangeListener() != null)) { |
| fireChangeEvents = true; |
| //Collections may not be indirect list or may have been replaced with user collection. |
| Object iterator = containerPolicy.iteratorFor(valueOfTarget); |
| while (containerPolicy.hasNext(iterator)) { |
| Map.Entry entry = (Map.Entry)containerPolicy.nextEntry(iterator, mergeManager.getSession()); |
| ((ObjectChangeListener)((ChangeTracker)target)._persistence_getPropertyChangeListener()).internalPropertyChange(new MapChangeEvent(target, getAttributeName(), valueOfTarget, entry.getKey(), entry.getValue(), CollectionChangeEvent.REMOVE, false));// make the remove change event fire. |
| } |
| if (newContainer instanceof ChangeTracker) { |
| ((ChangeTracker)newContainer)._persistence_setPropertyChangeListener(((ChangeTracker)target)._persistence_getPropertyChangeListener()); |
| } |
| if (valueOfTarget instanceof ChangeTracker) { |
| ((ChangeTracker)valueOfTarget)._persistence_setPropertyChangeListener(null);//remove listener |
| } |
| } |
| valueOfTarget = newContainer; |
| |
| for (Object sourceValuesIterator = containerPolicy.iteratorFor(valueOfSource); |
| containerPolicy.hasNext(sourceValuesIterator);) { |
| Map.Entry entry = (Map.Entry)containerPolicy.nextEntry(sourceValuesIterator, mergeManager.getSession()); |
| if (fireChangeEvents) { |
| //Collections may not be indirect list or may have been replaced with user collection. |
| ((ObjectChangeListener)((ChangeTracker)target)._persistence_getPropertyChangeListener()).internalPropertyChange(new MapChangeEvent(target, getAttributeName(), valueOfTarget, entry.getKey(), entry.getValue(), CollectionChangeEvent.ADD, false));// make the add change event fire. |
| } |
| containerPolicy.addInto(entry.getKey(), entry.getValue(), valueOfTarget, mergeManager.getSession()); |
| } |
| if (fireChangeEvents && (getDescriptor().getObjectChangePolicy().isAttributeChangeTrackingPolicy())) { |
| // check that there were changes, if not then remove the record. |
| ObjectChangeSet changeSet = ((AttributeChangeListener)((ChangeTracker)target)._persistence_getPropertyChangeListener()).getObjectChangeSet(); |
| if (changeSet != null) { |
| DirectMapChangeRecord changeRecord = (DirectMapChangeRecord)changeSet.getChangesForAttributeNamed(getAttributeName()); |
| if (changeRecord != null) { |
| if (!changeRecord.isDeferred()) { |
| if (!changeRecord.hasChanges()) { |
| changeSet.removeChange(getAttributeName()); |
| } |
| } else { |
| // Must reset the latest collection. |
| changeRecord.setLatestCollection(valueOfTarget); |
| } |
| } |
| } |
| } |
| |
| // Must re-set variable to allow for set method to re-morph changes if the collection is not being stored directly. |
| setRealAttributeValueInObject(target, valueOfTarget); |
| } |
| |
| /** |
| * INTERNAL: |
| * Perform the commit event. |
| * This is used in the uow to delay data modifications. |
| * This is mostly dealt with in the superclass. Private Owned deletes require extra functionality |
| */ |
| @Override |
| public void performDataModificationEvent(Object[] event, AbstractSession session) throws DatabaseException, DescriptorException { |
| super.performDataModificationEvent(event, session); |
| if (event[0] == Delete && containerPolicy.shouldIncludeKeyInDeleteEvent()) { |
| session.deleteObject(event[3]); |
| } |
| } |
| |
| /** |
| * 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. |
| DirectMapChangeRecord mapChangeRecord = (DirectMapChangeRecord)changeRecord; |
| |
| Iterator it = mapChangeRecord.getRemoveObjects().entrySet().iterator(); |
| while(it.hasNext()) { |
| Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>)it.next(); |
| containerPolicy.postCalculateChanges(entry.getKey(), entry.getValue(), referenceDescriptor, this, uow); |
| } |
| } |
| |
| |
| /** |
| * INTERNAL: |
| * Insert the private owned object. |
| */ |
| @Override |
| public void postInsert(WriteObjectQuery query) throws DatabaseException { |
| Object objects; |
| AbstractRecord databaseRow = new DatabaseRecord(); |
| |
| if (isReadOnly()) { |
| return; |
| } |
| |
| objects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession()); |
| if (this.containerPolicy.isEmpty(objects)) { |
| return; |
| } |
| |
| prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getDescriptor(), query.getSession()); |
| // Extract primary key and value from the source. |
| for (int index = 0; index < getReferenceKeyFields().size(); index++) { |
| DatabaseField referenceKey = getReferenceKeyFields().get(index); |
| DatabaseField sourceKey = getSourceKeyFields().get(index); |
| Object sourceKeyValue = query.getTranslationRow().get(sourceKey); |
| databaseRow.put(referenceKey, sourceKeyValue); |
| } |
| |
| // Extract target field and its value. Construct insert statement and execute it |
| Object keyIter = this.containerPolicy.iteratorFor(objects); |
| while (this.containerPolicy.hasNext(keyIter)) { |
| Map.Entry entry = (Map.Entry)this.containerPolicy.nextEntry(keyIter, query.getSession()); |
| Object value = getFieldValue(entry.getValue(), query.getSession()); |
| databaseRow.put(getDirectField(), value); |
| |
| ContainerPolicy.copyMapDataToRow(getContainerPolicy().getKeyMappingDataForWriteQuery(entry, query.getSession()), databaseRow); |
| // In the uow data queries are cached until the end of the commit. |
| if (query.shouldCascadeOnlyDependentParts()) { |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = new Object[3]; |
| event[0] = Insert; |
| event[1] = getInsertQuery(); |
| event[2] = databaseRow.clone(); |
| query.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } else { |
| query.getSession().executeQuery(getInsertQuery(), databaseRow); |
| } |
| getContainerPolicy().propogatePostInsert(query, entry); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Update private owned part. |
| */ |
| @Override |
| protected void postUpdateWithChangeSet(WriteObjectQuery writeQuery) throws DatabaseException { |
| ObjectChangeSet changeSet = writeQuery.getObjectChangeSet(); |
| DirectMapChangeRecord changeRecord = (DirectMapChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName()); |
| if (changeRecord == null) { |
| return; |
| } |
| for (int index = 0; index < getReferenceKeyFields().size(); index++) { |
| DatabaseField referenceKey = getReferenceKeyFields().get(index); |
| DatabaseField sourceKey = getSourceKeyFields().get(index); |
| Object sourceKeyValue = writeQuery.getTranslationRow().get(sourceKey); |
| writeQuery.getTranslationRow().put(referenceKey, sourceKeyValue); |
| } |
| for (Iterator iterator = changeRecord.getRemoveObjects().entrySet().iterator(); |
| iterator.hasNext();) { |
| Object entry = iterator.next(); |
| AbstractRecord thisRow = writeQuery.getTranslationRow().clone(); |
| ContainerPolicy.copyMapDataToRow(containerPolicy.getKeyMappingDataForWriteQuery(entry, writeQuery.getSession()), thisRow); |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = null; |
| if (containerPolicy.shouldIncludeKeyInDeleteEvent()){ |
| event = new Object[4]; |
| event[3] = containerPolicy.keyFromEntry(entry); |
| } else { |
| event = new Object[3]; |
| } |
| event[0] = Delete; |
| event[1] = getDeleteQuery(); |
| event[2] = thisRow; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } |
| for (Iterator iterator = changeRecord.getAddObjects().entrySet().iterator(); |
| iterator.hasNext();) { |
| Map.Entry entry = (Map.Entry)iterator.next(); |
| AbstractRecord thisRow = writeQuery.getTranslationRow().clone(); |
| Object value = changeRecord.getAddObjects().get(entry.getKey()); |
| value = getFieldValue(value, writeQuery.getSession()); |
| ContainerPolicy.copyMapDataToRow(this.containerPolicy.getKeyMappingDataForWriteQuery(entry, writeQuery.getSession()), thisRow); |
| thisRow.add(getDirectField(), value); |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = new Object[3]; |
| event[0] = Insert; |
| event[1] = getInsertQuery(); |
| event[2] = thisRow; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Propagate the preDelete event through the container policy if necessary |
| */ |
| @Override |
| public void preDelete(DeleteObjectQuery query) throws DatabaseException { |
| if (getContainerPolicy().propagatesEventsToCollection()){ |
| Object queryObject = query.getObject(); |
| Object values = getAttributeValueFromObject(queryObject); |
| Object iterator = containerPolicy.iteratorFor(values); |
| while (containerPolicy.hasNext(iterator)){ |
| Object wrappedObject = containerPolicy.nextEntry(iterator, query.getSession()); |
| containerPolicy.propogatePreDelete(query, wrappedObject); |
| } |
| } |
| super.preDelete(query); |
| } |
| |
| /** |
| * INTERNAL: |
| * Rebuild select query. |
| */ |
| @Override |
| protected void initOrRebuildSelectQuery() { |
| this.selectionQuery = containerPolicy.buildSelectionQueryForDirectCollectionMapping(); |
| } |
| |
| /** |
| * 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) { |
| // 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. |
| Iterator it = (Iterator) containerPolicy.iteratorFor(getRealAttributeValueFromObject(object, uow)); |
| while (it.hasNext()) { |
| Object clone = it.next(); |
| containerPolicy.recordPrivateOwnedRemovals(clone, referenceDescriptor, uow); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Remove a value and its change set from the collection change record. This is used by |
| * attribute change tracking. |
| */ |
| protected void removeFromCollectionChangeRecord(Object newKey, Object newValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) throws DescriptorException { |
| DirectMapChangeRecord collectionChangeRecord = (DirectMapChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName()); |
| if (collectionChangeRecord == null) { |
| collectionChangeRecord = new DirectMapChangeRecord(objectChangeSet); |
| collectionChangeRecord.setAttribute(getAttributeName()); |
| collectionChangeRecord.setMapping(this); |
| objectChangeSet.addChange(collectionChangeRecord); |
| } |
| collectionChangeRecord.addRemoveChange(newKey, newValue); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| public void setDirectKeyField(DatabaseField keyField) { |
| getMappedKeyMapContainerPolicy().setKeyField(keyField, descriptor); |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the class type of the field value. |
| * This can be used if field value differs from the object value, |
| * has specific typing requirements such as usage of java.sql.Blob or NChar. |
| * This must be called after the field name has been set. |
| */ |
| public void setDirectKeyFieldClassification(Class<?> fieldType) { |
| getDirectKeyField().setType(fieldType); |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the class type name of the field value. |
| * This can be used if field value differs from the object value, |
| * has specific typing requirements such as usage of java.sql.Blob or NChar. |
| * This must be called after the direct key field has been set. |
| */ |
| public void setDirectKeyFieldClassificationName(String fieldTypeName) { |
| getDirectKeyField().setTypeName(fieldTypeName); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the direct key field name in the reference table. |
| * This is the field that the primitive data value of the Map key is stored in. |
| */ |
| public void setDirectKeyFieldName(String fieldName) { |
| setDirectKeyField(new DatabaseField(fieldName)); |
| } |
| |
| /** |
| * 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) throws DescriptorException { |
| DirectMapChangeRecord collectionChangeRecord = (DirectMapChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName()); |
| if (collectionChangeRecord == null) { |
| collectionChangeRecord = new DirectMapChangeRecord(objectChangeSet); |
| collectionChangeRecord.setAttribute(getAttributeName()); |
| collectionChangeRecord.setMapping(this); |
| objectChangeSet.addChange(collectionChangeRecord); |
| } |
| if (collectionChangeRecord.getOriginalCollection() == null) { |
| collectionChangeRecord.recreateOriginalCollection(oldValue, uow); |
| } |
| collectionChangeRecord.setLatestCollection(newValue); |
| collectionChangeRecord.setIsDeferred(true); |
| |
| objectChangeSet.deferredDetectionRequiredOn(getAttributeName()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Add or removes a new value and its change set to the collection change record based on the event passed in. This is used by |
| * attribute change tracking. |
| */ |
| @Override |
| public void updateCollectionChangeRecord(CollectionChangeEvent event, ObjectChangeSet changeSet, UnitOfWorkImpl uow) { |
| if (event != null ) { |
| //Letting the mapping create and add the ChangeSet to the ChangeRecord rather |
| // than the policy, since the policy doesn't know how to handle DirectCollectionChangeRecord. |
| // if ordering is to be supported in the future, check how the method in CollectionMapping is implemented |
| Object key = null; |
| if (event.getClass().equals(ClassConstants.MapChangeEvent_Class)){ |
| key = ((MapChangeEvent)event).getKey(); |
| } |
| |
| if (event.getChangeType() == CollectionChangeEvent.ADD) { |
| addToCollectionChangeRecord(key, event.getNewValue(), changeSet, uow); |
| } else if (event.getChangeType() == CollectionChangeEvent.REMOVE) { |
| removeFromCollectionChangeRecord(key, event.getNewValue(), changeSet, uow); |
| } else { |
| throw ValidationException.wrongCollectionChangeEventType(event.getChangeType()); |
| } |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Configure the mapping to use an instance of the specified container class |
| * to hold the target objects. |
| * <p>The default container class is java.util.Hashtable. |
| * <p>The container class must implements (directly or indirectly) the Map interface. |
| * <p>Note: Do not use both useMapClass(Class concreteClass), useTransparentMap(). The last use of one of the two methods will override the previous one. |
| */ |
| @Override |
| public void useMapClass(Class<?> concreteClass) { |
| if (!Helper.classImplementsInterface(concreteClass, ClassConstants.Map_Class)) { |
| throw DescriptorException.illegalContainerClass(concreteClass); |
| } |
| containerPolicy.setContainerClass(concreteClass); |
| } |
| |
| /** |
| * PUBLIC: |
| * Configure the mapping to use an instance of the specified container class |
| * to hold the target objects. |
| * <p>The container class must implement (directly or indirectly) the Map interface. |
| * <p>Note: Do not use both useMapClass(Class concreteClass), useTransparentMap(). The last use of one of the two methods will override the previous one. |
| */ |
| public void useTransparentMap() { |
| setIndirectionPolicy(new TransparentIndirectionPolicy()); |
| useMapClass(ClassConstants.IndirectMap_Class); |
| } |
| |
| /** |
| * PUBLIC: |
| * This is a helper method to set the key converter to a TypeConversionConverter. |
| * This ensures that the key value from the database is converted to the correct |
| * Java type. The converter can also be set directly. |
| * Note that setting the converter to another converter will overwrite this setting. |
| */ |
| public void setKeyClass(Class<?> keyClass) { |
| TypeConversionConverter converter = new TypeConversionConverter(this); |
| converter.setObjectClass(keyClass); |
| setKeyConverter(converter); |
| } |
| |
| /** |
| * PUBLIC: |
| * This is a helper method to get the object class from the key converter |
| * if it is a TypeConversionConverter. |
| * This returns null if not using a TypeConversionConverter key converter. |
| */ |
| public Class<?> getKeyClass() { |
| if ((getKeyConverter() == null) || !(getKeyConverter() instanceof TypeConversionConverter)) { |
| return null; |
| } |
| return ((TypeConversionConverter)getKeyConverter()).getObjectClass(); |
| } |
| |
| /** |
| * PUBLIC: |
| * This is a helper method to set the value converter to a TypeConversionConverter. |
| * This ensures that the value from the database is converted to the correct |
| * Java type. The converter can also be set directly. |
| * Note that setting the converter to another converter will overwrite this setting. |
| */ |
| public void setValueClass(Class<?> valueClass) { |
| TypeConversionConverter converter = new TypeConversionConverter(this); |
| converter.setObjectClass(valueClass); |
| setValueConverter(converter); |
| } |
| |
| /** |
| * ADVANCED: |
| * This method is used to have an object add to a collection once the changeSet is applied |
| * The referenceKey parameter should only be used for direct Maps. |
| */ |
| @Override |
| public void simpleAddToCollectionChangeRecord(Object referenceKey, Object objectToAdd, ObjectChangeSet changeSet, AbstractSession session) { |
| DirectMapChangeRecord collectionChangeRecord = (DirectMapChangeRecord)changeSet.getChangesForAttributeNamed(getAttributeName()); |
| if (collectionChangeRecord == null) { |
| collectionChangeRecord = new DirectMapChangeRecord(changeSet); |
| collectionChangeRecord.setAttribute(getAttributeName()); |
| collectionChangeRecord.setMapping(this); |
| collectionChangeRecord.getAddObjects().put(referenceKey, objectToAdd); |
| changeSet.addChange(collectionChangeRecord); |
| } else { |
| if (collectionChangeRecord.getRemoveObjects().containsKey(referenceKey)) { |
| collectionChangeRecord.getRemoveObjects().remove(referenceKey); |
| } else { |
| collectionChangeRecord.getAddObjects().put(referenceKey, objectToAdd); |
| } |
| } |
| } |
| |
| /** |
| * ADVANCED: |
| * This method is used to have an object removed from a collection once the changeSet is applied |
| * The referenceKey parameter should only be used for direct Maps. |
| */ |
| @Override |
| public void simpleRemoveFromCollectionChangeRecord(Object referenceKey, Object objectToRemove, ObjectChangeSet changeSet, AbstractSession session) { |
| DirectMapChangeRecord collectionChangeRecord = (DirectMapChangeRecord)changeSet.getChangesForAttributeNamed(getAttributeName()); |
| if (collectionChangeRecord == null) { |
| collectionChangeRecord = new DirectMapChangeRecord(changeSet); |
| collectionChangeRecord.setAttribute(getAttributeName()); |
| collectionChangeRecord.setMapping(this); |
| collectionChangeRecord.getRemoveObjects().put(referenceKey, objectToRemove); |
| changeSet.addChange(collectionChangeRecord); |
| } else { |
| if (collectionChangeRecord.getAddObjects().containsKey(referenceKey)) { |
| collectionChangeRecord.getAddObjects().remove(referenceKey); |
| } else { |
| collectionChangeRecord.getRemoveObjects().put(referenceKey, objectToRemove); |
| } |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * This is a helper method to get the object class from the value converter |
| * if it is a TypeConversionConverter. |
| * This returns null if not using a TypeConversionConverter value converter. |
| */ |
| public Class<?> getValueClass() { |
| if (!(getValueConverter() instanceof TypeConversionConverter)) { |
| return null; |
| } |
| return ((TypeConversionConverter)getValueConverter()).getObjectClass(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare and execute the batch query and store the |
| * results for each source object in a map keyed by the |
| * mappings source keys of the source objects. |
| */ |
| @Override |
| protected void executeBatchQuery(DatabaseQuery query, CacheKey parentCacheKey, Map referenceDataByKey, AbstractSession session, AbstractRecord translationRow) { |
| // Execute query and index resulting object sets by key. |
| List<AbstractRecord> rows = (List)session.executeQuery(query, translationRow); |
| MappedKeyMapContainerPolicy mapContainerPolicy = getMappedKeyMapContainerPolicy(); |
| for (AbstractRecord referenceRow : rows) { |
| Object referenceKey = null; |
| if (query.isObjectBuildingQuery()){ |
| referenceKey = mapContainerPolicy.buildKey(referenceRow, (ObjectBuildingQuery)query, parentCacheKey, session, true); |
| } else { |
| referenceKey = mapContainerPolicy.buildKey(referenceRow, null, parentCacheKey, session, true); |
| } |
| Object referenceValue = referenceRow.get(this.directField); |
| Object eachCacheKey = extractKeyFromTargetRow(referenceRow, session); |
| |
| Object container = referenceDataByKey.get(eachCacheKey); |
| if ((container == null) || (container == Helper.NULL_VALUE)) { |
| container = this.containerPolicy.containerInstance(); |
| referenceDataByKey.put(eachCacheKey, container); |
| } |
| |
| // Allow for value conversion. |
| if (this.valueConverter != null) { |
| referenceValue = this.valueConverter.convertDataValueToObjectValue(referenceValue, query.getSession()); |
| } |
| |
| this.containerPolicy.addInto(referenceKey, referenceValue, container, query.getSession()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the value of the field from the row or a value holder on the query to obtain the object. |
| */ |
| @Override |
| protected Object valueFromRowInternalWithJoin(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, CacheKey parentCacheKey, AbstractSession executionSession, boolean isTargetProtected) throws DatabaseException { |
| |
| ContainerPolicy policy = getContainerPolicy(); |
| Object value = policy.containerInstance(); |
| ObjectBuilder objectBuilder = getDescriptor().getObjectBuilder(); |
| // Extract the primary key of the source object, to filter only the joined rows for that object. |
| Object sourceKey = objectBuilder.extractPrimaryKeyFromRow(row, executionSession); |
| // If the query was using joining, all of the result rows by primary key will have been computed. |
| List<AbstractRecord> rows = joinManager.getDataResultsByPrimaryKey().get(sourceKey); |
| // If no 1-m rows were fetch joined, then get the value normally, |
| // this can occur with pagination where the last row may not be complete. |
| if (rows == null) { |
| return valueFromRowInternal(row, joinManager, sourceQuery, executionSession); |
| } |
| // A set of direct values must be maintained to avoid duplicates from multiple 1-m joins. |
| Set directValues = new HashSet(); |
| |
| Converter valueConverter = getValueConverter(); |
| // For each rows, extract the target row and build the target object and add to the collection. |
| int size = rows.size(); |
| for (int index = 0; index < size; index++) { |
| AbstractRecord sourceRow = rows.get(index); |
| AbstractRecord targetRow = sourceRow; |
| // The field for many objects may be in the row, |
| // so build the subpartion of the row through the computed values in the query, |
| // this also helps the field indexing match. |
| targetRow = trimRowForJoin(targetRow, joinManager, executionSession); |
| // Partial object queries must select the primary key of the source and related objects. |
| // If the target joined rows in null (outerjoin) means an empty collection. |
| Object directKey = this.containerPolicy.buildKeyFromJoinedRow(targetRow, joinManager, sourceQuery, parentCacheKey, executionSession, isTargetProtected); |
| if (directKey == null) { |
| // A null direct value means an empty collection returned as nulls from an outerjoin. |
| return getIndirectionPolicy().valueFromRow(value); |
| } |
| // Only build/add the target object once, skip duplicates from multiple 1-m joins. |
| if (!directValues.contains(directKey)) { |
| directValues.add(directKey); |
| Object directValue = targetRow.get(this.directField); |
| // Allow for value conversion. |
| if (valueConverter != null) { |
| directValue = valueConverter.convertDataValueToObjectValue(directValue, executionSession); |
| } |
| policy.addInto(directKey, directValue, value, executionSession); |
| } |
| } |
| return getIndirectionPolicy().valueFromRow(value); |
| } |
| } |