/******************************************************************************* * Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * 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, (Object)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); } }