/******************************************************************************* | |
* Copyright (c) 1998, 2013 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: | |
* tware - initial implementation | |
* tware - implemenation of basic CRUD functionality | |
* 11/10/2011-2.4 Guy Pelletier | |
* - 357474: Address primaryKey option from tenant discriminator column | |
* 14/05/2012-2.4 Guy Pelletier | |
* - 376603: Provide for table per tenant support for multitenant applications | |
******************************************************************************/ | |
package org.eclipse.persistence.internal.queries; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collection; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Map.Entry; | |
import java.util.Set; | |
import org.eclipse.persistence.annotations.CacheKeyType; | |
import org.eclipse.persistence.descriptors.ClassDescriptor; | |
import org.eclipse.persistence.exceptions.DescriptorException; | |
import org.eclipse.persistence.exceptions.QueryException; | |
import org.eclipse.persistence.exceptions.ValidationException; | |
import org.eclipse.persistence.expressions.Expression; | |
import org.eclipse.persistence.internal.core.queries.CoreMappedKeyMapContainerPolicy; | |
import org.eclipse.persistence.internal.descriptors.DescriptorIterator; | |
import org.eclipse.persistence.internal.helper.DatabaseField; | |
import org.eclipse.persistence.internal.helper.DatabaseTable; | |
import org.eclipse.persistence.internal.identitymaps.CacheId; | |
import org.eclipse.persistence.internal.identitymaps.CacheKey; | |
import org.eclipse.persistence.internal.sessions.AbstractRecord; | |
import org.eclipse.persistence.internal.sessions.AbstractSession; | |
import org.eclipse.persistence.internal.sessions.CollectionChangeRecord; | |
import org.eclipse.persistence.internal.sessions.MergeManager; | |
import org.eclipse.persistence.internal.sessions.ObjectChangeSet; | |
import org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet; | |
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; | |
import org.eclipse.persistence.mappings.AggregateObjectMapping; | |
import org.eclipse.persistence.mappings.Association; | |
import org.eclipse.persistence.mappings.CollectionMapping; | |
import org.eclipse.persistence.mappings.DatabaseMapping; | |
import org.eclipse.persistence.mappings.DirectMapMapping; | |
import org.eclipse.persistence.mappings.DirectToFieldMapping; | |
import org.eclipse.persistence.mappings.ForeignReferenceMapping; | |
import org.eclipse.persistence.mappings.converters.Converter; | |
import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping; | |
import org.eclipse.persistence.mappings.foundation.MapKeyMapping; | |
import org.eclipse.persistence.mappings.foundation.MapComponentMapping; | |
import org.eclipse.persistence.mappings.querykeys.QueryKey; | |
import org.eclipse.persistence.queries.DataReadQuery; | |
import org.eclipse.persistence.queries.DatabaseQuery; | |
import org.eclipse.persistence.queries.DeleteObjectQuery; | |
import org.eclipse.persistence.queries.ObjectBuildingQuery; | |
import org.eclipse.persistence.queries.ObjectLevelReadQuery; | |
import org.eclipse.persistence.queries.ReadAllQuery; | |
import org.eclipse.persistence.queries.ReadQuery; | |
import org.eclipse.persistence.queries.WriteObjectQuery; | |
import org.eclipse.persistence.sessions.DatabaseRecord; | |
/** | |
* A MappedKeyMapContainerPolicy should be used for mappings to implementers of Map. | |
* It differs from MapContainerPolicy by allowing the MapKey to be an otherwise unmapped | |
* column in a table rather than a mapped element of the value in the map. | |
* | |
* This container policy holds a reference to a KeyMapping that will be used to construct the key | |
* from the database and a reference to its owner which creates the value for the map. | |
* | |
* The key of the map can be any implementer of MapKeyMapping and the data representing the | |
* key can either be stored in the target table of the value mapping, or in a collection table that | |
* associates the source to the target. The data can either be everything necessary to compose the | |
* key, or foreign keys that allow the key to be retrieved | |
* | |
* @see MapContainerPolicy | |
* @see MapKeyMapping | |
* @see MapComponentMapping | |
* | |
* @author tware | |
* | |
*/ | |
public class MappedKeyMapContainerPolicy extends MapContainerPolicy implements CoreMappedKeyMapContainerPolicy<AbstractSession> { | |
protected MapKeyMapping keyMapping; | |
protected MapComponentMapping valueMapping; | |
public DatabaseQuery keyQuery; | |
/** | |
* INTERNAL: | |
* Construct a new policy. | |
*/ | |
public MappedKeyMapContainerPolicy() { | |
super(); | |
} | |
/** | |
* INTERNAL: | |
* Construct a new policy for the specified class. | |
*/ | |
public MappedKeyMapContainerPolicy(Class containerClass) { | |
super(containerClass); | |
} | |
/** | |
* INTERNAL: | |
* Construct a new policy for the specified class name. | |
*/ | |
public MappedKeyMapContainerPolicy(String containerClassName) { | |
super(containerClassName); | |
} | |
/** | |
* INTERNAL: | |
* Called when the selection query is being initialize to add the fields for the key to the query | |
*/ | |
@Override | |
public void addAdditionalFieldsToQuery(ReadQuery selectionQuery, Expression baseExpression) { | |
keyMapping.addAdditionalFieldsToQuery(selectionQuery, baseExpression); | |
} | |
/** | |
* INTERNAL: | |
* Add any non-Foreign-key data from an Object describe by a MapKeyMapping to a database row | |
* This is typically used in write queries to ensure all the data stored in the collection table is included | |
* in the query. | |
*/ | |
@Override | |
public Map getKeyMappingDataForWriteQuery(Object object, AbstractSession session) { | |
if (((DatabaseMapping)keyMapping).isReadOnly()) { | |
return null; | |
} | |
Object keyValue = ((Map.Entry)object).getKey(); | |
return keyMapping.extractIdentityFieldsForQuery(keyValue, session); | |
} | |
/** | |
* INTERNAL: | |
* Return the type of the map key, this will be overridden by container policies that allow maps. | |
*/ | |
@Override | |
public Object getKeyType() { | |
return keyMapping.getMapKeyTargetType(); | |
} | |
/** | |
* INTERNAL: | |
* Called when the insert query is being initialized to ensure the fields for the key are in the insert query | |
* | |
* @see MappedKeyMapContainerPolicy | |
*/ | |
@Override | |
public void addFieldsForMapKey(AbstractRecord joinRow) { | |
if (((DatabaseMapping)keyMapping).isReadOnly()) { | |
return; | |
} | |
keyMapping.addFieldsForMapKey(joinRow); | |
} | |
/** | |
* INTERNAL: | |
* Add element into container which implements the Map interface. | |
* The may be used by merging/cloning passing a Map.Entry. | |
*/ | |
@Override | |
public boolean addInto(Object element, Object container, AbstractSession session) { | |
if (element instanceof Map.Entry) { | |
Map.Entry record = (Map.Entry)element; | |
Object key = record.getKey(); | |
Object value = record.getValue(); | |
return addInto(key, value, container, session); | |
} | |
throw QueryException.cannotAddToContainer(element, container, this); | |
} | |
/** | |
* INTERNAL: | |
* This is used for ordered List containers to add all of the elements | |
* to the collection in the order of the index field in the row. | |
* This is currently only used by OrderListContainerPolicy, so this is just a stub. | |
* The passing of the query is to allow future compatibility with Maps (ordered Map). | |
*/ | |
@Override | |
public boolean addInto(Object element, Object container, AbstractSession session, AbstractRecord row, DataReadQuery query, CacheKey parentCacheKey, boolean isTargetProtected) { | |
Object key = this.keyMapping.createMapComponentFromRow(row, null, parentCacheKey, session, isTargetProtected); | |
Object value = this.valueMapping.createMapComponentFromRow(row, null, parentCacheKey, session, isTargetProtected); | |
return addInto(key, value, container, session); | |
} | |
/** | |
* INTERNAL: | |
* Add element to that implements the Map interface | |
* use the row to compute the key | |
*/ | |
@Override | |
public boolean addInto(Object element, Object container, AbstractSession session, AbstractRecord dbRow, ObjectBuildingQuery query, CacheKey parentCacheKey, boolean isTargetProtected) { | |
Object key = null; | |
Object value = null; | |
// we are a direct collection mapping. This means the key will be element and the value will come | |
// from dbRow | |
if ((valueMapping != null) && (((DatabaseMapping)valueMapping).isDirectCollectionMapping()) && (session.getDescriptor(element.getClass()) != null)) { | |
key = element; | |
value = valueMapping.createMapComponentFromRow(dbRow, null, parentCacheKey, session, isTargetProtected); | |
} else if (keyMapping != null) { | |
value = element; | |
try{ | |
key = keyMapping.createMapComponentFromRow(dbRow, query, parentCacheKey, session, isTargetProtected); | |
} catch (Exception e) { | |
throw QueryException.exceptionWhileReadingMapKey(element, e); | |
} | |
} | |
return addInto(key, value, container, session); | |
} | |
/** | |
* INTERNAL: | |
* Used for joining. Add any queries necessary for joining to the join manager | |
*/ | |
@Override | |
public void addNestedJoinsQueriesForMapKey(JoinedAttributeManager joinManager, ObjectLevelReadQuery query, AbstractSession session){ | |
ObjectLevelReadQuery nestedQuery = keyMapping.getNestedJoinQuery(joinManager, query, session); | |
if (nestedQuery != null){ | |
joinManager.getJoinedMappingQueries_().put((DatabaseMapping)keyMapping, nestedQuery); | |
} | |
} | |
/** | |
* Build a clone for the key of a Map represented by this container policy. | |
*/ | |
@Override | |
public Object buildCloneForKey(Object key, Object parent, CacheKey parentCacheKey, Integer refreshCascade, AbstractSession cloningSession, boolean isExisting, boolean isCacheCheckComplete){ | |
return keyMapping.buildElementClone(key, parent, parentCacheKey, refreshCascade, cloningSession, isExisting, isCacheCheckComplete); | |
} | |
/** | |
* INTERNAL: | |
* Certain key mappings favor different types of selection query. Return the appropriate | |
* type of selectionQuery. | |
*/ | |
@Override | |
public ReadQuery buildSelectionQueryForDirectCollectionMapping(){ | |
ReadQuery query = keyMapping.buildSelectionQueryForDirectCollectionKeyMapping(this); | |
return query; | |
} | |
/** | |
* Extract the key for the map from the provided row. | |
*/ | |
@Override | |
public Object buildKey(AbstractRecord row, ObjectBuildingQuery query, CacheKey parentCacheKey, AbstractSession session, boolean isTargetProtected){ | |
return keyMapping.createMapComponentFromRow(row, query, parentCacheKey, session, isTargetProtected); | |
} | |
/** | |
* Extract the key for the map from the provided row. | |
*/ | |
@Override | |
public Object buildKeyFromJoinedRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery query, CacheKey parentCacheKey, AbstractSession session, boolean isTargetProtected){ | |
return keyMapping.createMapComponentFromJoinedRow(row, joinManager, query, parentCacheKey, session, isTargetProtected); | |
} | |
/** | |
* INTERNAL: | |
* This method will access the target relationship and create a list of information to rebuild the collection. | |
* For the MapContainerPolicy this return will consist of an array with serial Map entry key and value elements. | |
* @see ObjectReferenceMapping.buildReferencesPKList | |
* @see ContainerPolicy.buildReferencesPKList | |
*/ | |
@Override | |
public Object[] buildReferencesPKList(Object container, AbstractSession session){ | |
Object[] result = new Object[this.sizeFor(container)*2]; | |
Iterator iterator = (Iterator)this.iteratorFor(container); | |
boolean isElementCollection = ((DatabaseMapping)valueMapping).isElementCollectionMapping(); | |
int index = 0; | |
while(iterator.hasNext()){ | |
Map.Entry entry = (Entry) iterator.next(); | |
result[index] = keyMapping.createSerializableMapKeyInfo(entry.getKey(), session); | |
++index; | |
if (isElementCollection) { | |
result[index] = entry.getValue(); | |
} else { | |
result[index] = elementDescriptor.getObjectBuilder().extractPrimaryKeyFromObject(entry.getValue(), session); | |
} | |
++index; | |
} | |
return result; | |
} | |
/** | |
* INTERNAL: | |
* Cascade discover and persist new objects during commit to the map key | |
*/ | |
@Override | |
public void cascadeDiscoverAndPersistUnregisteredNewObjects(Object object, Map newObjects, Map unregisteredExistingObjects, Map visitedObjects, UnitOfWorkImpl uow, Set cascadeErrors) { | |
keyMapping.cascadeDiscoverAndPersistUnregisteredNewObjects(((Map.Entry)object).getKey(), newObjects, unregisteredExistingObjects, visitedObjects, uow, false, cascadeErrors); | |
} | |
/** | |
* INTERNAL: | |
* Cascade registerNew to any mappings managed by the container policy. This will cascade the register to the key mapping. | |
*/ | |
@Override | |
public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { | |
keyMapping.cascadePerformRemoveIfRequired(((Map.Entry)object).getKey(), uow, visitedObjects, false); | |
} | |
/** | |
* INTERNAL: | |
* Cascade registerNew to any mappings managed by the container policy. This will cascade the register to the key mapping. | |
*/ | |
@Override | |
public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { | |
keyMapping.cascadeRegisterNewIfRequired(((Map.Entry)object).getKey(), uow, visitedObjects, false); | |
} | |
/** | |
* INTERNAL: | |
* The mapping clones itself to create deep copy. | |
*/ | |
@Override | |
public Object clone() { | |
MappedKeyMapContainerPolicy clone = (MappedKeyMapContainerPolicy) super.clone(); | |
clone.keyMapping = (MapKeyMapping) this.keyMapping.clone(); | |
if (this.keyQuery != null) { | |
clone.keyQuery = (DatabaseQuery) this.keyQuery.clone(); | |
} | |
return clone; | |
} | |
/** | |
* INTERNAL: | |
* Return true if keys are the same. False otherwise | |
*/ | |
public boolean compareContainers(Object firstObjectMap, Object secondObjectMap) { | |
if (sizeFor(firstObjectMap) != sizeFor(secondObjectMap)) { | |
return false; | |
} | |
for (Object firstIterator = iteratorFor(firstObjectMap); hasNext(firstIterator);) { | |
Map.Entry entry = (Map.Entry)nextEntry(firstIterator); | |
Object key = entry.getKey(); | |
if (!((Map)firstObjectMap).get(key).equals(((Map)secondObjectMap).get(key))) { | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* INTERNAL: | |
* Return true if keys are the same in the source as the backup. False otherwise | |
* in the case of read-only compare against the original. | |
*/ | |
@Override | |
public boolean compareKeys(Object sourceValue, AbstractSession session) { | |
// Key is not stored in the object, only in the Map and the DB | |
// As a result, a change in the object will not change how this object is hashed | |
if (keyMapping != null){ | |
return true; | |
} | |
return super.compareKeys(sourceValue, session); | |
} | |
/** | |
* INTERNAL: | |
* Create change sets that contain map keys. | |
*/ | |
@Override | |
protected void createChangeSetForKeys(Map originalKeyValues, CollectionChangeRecord changeRecord, AbstractSession session, ClassDescriptor referenceDescriptor){ | |
Iterator originalKeyValuesIterator = originalKeyValues.values().iterator(); | |
while (originalKeyValuesIterator.hasNext()){ | |
Association association = (Association)originalKeyValuesIterator.next(); | |
Object object = association.getValue(); | |
ObjectChangeSet changeSet = referenceDescriptor.getObjectBuilder().createObjectChangeSet(object, (UnitOfWorkChangeSet) changeRecord.getOwner().getUOWChangeSet(), session); | |
changeSet.setOldKey(association.getKey()); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Create a query key that links to the map key. | |
*/ | |
@Override | |
public QueryKey createQueryKeyForMapKey() { | |
return keyMapping.createQueryKeyForMapKey(); | |
} | |
/** | |
* INTERNAL: | |
* This method will actually potentially wrap an object in two ways. It will first wrap the object | |
* based on the referenceDescriptor's wrapper policy. It will also potentially do some wrapping based | |
* on what is required by the container policy. | |
*/ | |
@Override | |
public Object createWrappedObjectFromExistingWrappedObject(Object wrappedObject, Object parent, ClassDescriptor referenceDescriptor, MergeManager mergeManager, AbstractSession targetSession){ | |
Object key = ((Map.Entry)wrappedObject).getKey(); | |
key = keyMapping.getTargetVersionOfSourceObject(key, parent, mergeManager, targetSession); | |
key = keyMapping.wrapKey(key, mergeManager.getSession()); | |
Object value = referenceDescriptor.getObjectBuilder().wrapObject(mergeManager.getTargetVersionOfSourceObject(unwrapIteratorResult(wrappedObject), referenceDescriptor, targetSession), mergeManager.getSession()); | |
return new Association(key, value); | |
} | |
/** | |
* INTERNAL: | |
* Convert all the class-name-based settings in this ContainerPolicy to actual class-based | |
* settings | |
* @param classLoader | |
*/ | |
@Override | |
public void convertClassNamesToClasses(ClassLoader classLoader) { | |
((DatabaseMapping)keyMapping).convertClassNamesToClasses(classLoader); | |
} | |
/** | |
* INTERNAL: | |
* Delete the key and value of the passed association passed object. | |
*/ | |
@Override | |
public void deleteWrappedObject(Object objectDeleted, AbstractSession session) { | |
if (((DatabaseMapping)keyMapping).isPrivateOwned()){ | |
keyMapping.deleteMapKey(((Map.Entry)objectDeleted).getKey(), session); | |
} | |
session.deleteObject(unwrapIteratorResult(objectDeleted)); | |
} | |
/** | |
* INTERNAL: | |
* Return any tables that will be required when this mapping is used as part of a join query. | |
*/ | |
@Override | |
public List<DatabaseTable> getAdditionalTablesForJoinQuery() { | |
return keyMapping.getAdditionalTablesForJoinQuery(); | |
} | |
/** | |
* INTERNAL: | |
* Return any additional fields required by the policy for a fetch join. | |
*/ | |
@Override | |
public List<DatabaseField> getAdditionalFieldsForJoin(CollectionMapping baseMapping) { | |
return keyMapping.getAllFieldsForMapKey(); | |
} | |
/** | |
* INTERNAL: | |
* Return a Map of any foreign keys defined within the the MapKey. | |
*/ | |
public Map<DatabaseField, DatabaseField> getForeignKeyFieldsForMapKey() { | |
return keyMapping.getForeignKeyFieldsForMapKey(); | |
} | |
/** | |
* INTERNAL: | |
* Return the reference descriptor for the map key if it exists. | |
*/ | |
@Override | |
public ClassDescriptor getDescriptorForMapKey() { | |
return keyMapping.getReferenceDescriptor(); | |
} | |
/** | |
* INTERNAL: | |
* Used when objects are added or removed during an update. | |
* This method returns either the clone from the ChangeSet or a packaged | |
* version of it that contains things like map keys. | |
*/ | |
@Override | |
public Object getCloneDataFromChangeSet(ObjectChangeSet changeSet) { | |
Object key = changeSet.getNewKey(); | |
if (key == null) { | |
key = changeSet.getOldKey(); | |
} | |
return new Association(key ,changeSet.getUnitOfWorkClone()); | |
} | |
/** | |
* INTERNAL: | |
* Return the DatabaseField that represents the key in a DirectMapMapping. If the | |
* keyMapping is not a DirectMapping, this will return null. | |
*/ | |
@Override | |
public DatabaseField getDirectKeyField(CollectionMapping baseMapping) { | |
if ((keyMapping != null) && ((DatabaseMapping)keyMapping).isDirectToFieldMapping()) { | |
return ((AbstractDirectMapping)keyMapping).getField(); | |
} | |
return null; | |
} | |
/** | |
* INTERNAL: | |
* Return the fields that make up the identity of the mapped object. For mappings with | |
* a primary key, it will be the set of fields in the primary key. For mappings without | |
* a primary key it will likely be all the fields. | |
*/ | |
@Override | |
public List<DatabaseField> getIdentityFieldsForMapKey() { | |
return keyMapping.getIdentityFieldsForMapKey(); | |
} | |
/** | |
* INTERNAL: | |
* Get the Converter for the key of this mapping if one exists. | |
*/ | |
public Converter getKeyConverter() { | |
if ((keyMapping != null) && ((DatabaseMapping)keyMapping).isDirectToFieldMapping()) { | |
return ((AbstractDirectMapping)keyMapping).getConverter(); | |
} | |
return null; | |
} | |
public MapKeyMapping getKeyMapping() { | |
return keyMapping; | |
} | |
/** | |
* INTERNAL: | |
* Some map keys must be obtained from the database. This query is used to obtain the key. | |
*/ | |
public DatabaseQuery getKeyQuery() { | |
return keyQuery; | |
} | |
/** | |
* INTERNAL: | |
* Get the selection criteria for the map key. | |
*/ | |
@Override | |
public Expression getKeySelectionCriteria() { | |
return keyMapping.getAdditionalSelectionCriteriaForMapKey(); | |
} | |
public MapComponentMapping getValueMapping(){ | |
return valueMapping; | |
} | |
/** | |
* INTERNAL: | |
* Initialize the key mapping | |
*/ | |
@Override | |
public void initialize(AbstractSession session, DatabaseTable keyTable) { | |
getKeyMapping().preinitializeMapKey(keyTable); | |
((DatabaseMapping)keyMapping).initialize(session); | |
} | |
/** | |
* CollectionTableMapContainerPolicy is for mappings where the key is stored in a table separately from the map | |
* element. | |
*/ | |
@Override | |
protected boolean isKeyAvailableFromElement() { | |
return false; | |
} | |
@Override | |
public boolean isMappedKeyMapPolicy() { | |
return true; | |
} | |
/** | |
* INTERNAL: | |
* Return whether a map key this container policy represents is an attribute | |
* By default this method will return false since only subclasses actually represent maps. | |
*/ | |
@Override | |
public boolean isMapKeyAttribute() { | |
return ((DatabaseMapping)keyMapping).isAbstractDirectMapping(); | |
} | |
/** | |
* INTERNAL: | |
* Return if the map key this container policy represents is a OneToOne. | |
*/ | |
@Override | |
public boolean isMapKeyObject() { | |
return ((DatabaseMapping)keyMapping).isOneToOneMapping(); | |
} | |
/** | |
* INTERNAL: | |
* Used in Descriptor Iteration to iterate on map keys. | |
*/ | |
@Override | |
public void iterateOnMapKey(DescriptorIterator iterator, Object element) { | |
Object key = ((Map.Entry)element).getKey(); | |
keyMapping.iterateOnMapKey(iterator, key); | |
} | |
/** | |
* INTERNAL: | |
* Add the provided object to the deleted objects list on the commit manager. | |
* This may be overridden by subclasses to process a composite object. | |
*/ | |
@Override | |
public void postCalculateChanges(ObjectChangeSet ocs, ClassDescriptor referenceDescriptor, DatabaseMapping mapping, UnitOfWorkImpl uow) { | |
if (((DatabaseMapping)getKeyMapping()).isForeignReferenceMapping() && ((DatabaseMapping)getKeyMapping()).isPrivateOwned()) { | |
Object key = ocs.getOldKey(); | |
uow.addDeletedPrivateOwnedObjects((DatabaseMapping)getKeyMapping(), key); | |
} | |
super.postCalculateChanges(ocs, referenceDescriptor, mapping, uow); | |
} | |
/** | |
* INTERNAL: | |
* Add the provided object to the deleted objects list on the commit manager. | |
* This may be overridden by subclasses to process a composite object. | |
*/ | |
@Override | |
public void postCalculateChanges(Object key, Object value, ClassDescriptor referenceDescriptor, DatabaseMapping mapping, UnitOfWorkImpl uow) { | |
if (((DatabaseMapping)getKeyMapping()).isForeignReferenceMapping() && ((DatabaseMapping)getKeyMapping()).isPrivateOwned()) { | |
uow.addDeletedPrivateOwnedObjects((DatabaseMapping)getKeyMapping(), key); | |
} | |
super.postCalculateChanges(key, value, referenceDescriptor, mapping, uow); | |
} | |
/** | |
* INTERNAL: | |
* This method is used to check the key mapping to ensure that it does not write to | |
* a field that is written by another mapping. There are two possibilities: | |
* | |
* 1. The conflicting mapping has already been processed. In that case, we add MultipleWritableMappings | |
* exception to the integrity checker right away | |
* 2. There are no conflicting mappings. In that case, we store the list of fields that this mapping | |
* has processed on the descriptor for the target so they can be checked as the descriptor initializes. | |
*/ | |
@Override | |
public void processAdditionalWritableMapKeyFields(AbstractSession session) { | |
if (!((DatabaseMapping)getKeyMapping()).isReadOnly() && (this.valueMapping instanceof CollectionMapping)) { | |
CollectionMapping mapping = (CollectionMapping)valueMapping; | |
Iterator<DatabaseField> i = getIdentityFieldsForMapKey().iterator(); | |
while (i.hasNext()){ | |
DatabaseField field = i.next(); | |
if (mapping.getReferenceDescriptor().getObjectBuilder().getMappingsByField().containsKey(field) || mapping.getReferenceDescriptor().getAdditionalWritableMapKeyFields().contains(field)) { | |
session.getIntegrityChecker().handleError(DescriptorException.multipleWriteMappingsForField(field.toString(), mapping)); | |
} else { | |
mapping.getReferenceDescriptor().getAdditionalWritableMapKeyFields().add(field); | |
} | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* Add the key and value from provided association to the deleted objects list on the commit manager. | |
*/ | |
@Override | |
public void recordPrivateOwnedRemovals(Object object,ClassDescriptor referenceDescriptor, UnitOfWorkImpl uow){ | |
if (((DatabaseMapping)keyMapping).isPrivateOwned()){ | |
Object key = ((Map.Entry)object).getKey(); | |
((DatabaseMapping)keyMapping).getReferenceDescriptor().getObjectBuilder().recordPrivateOwnedRemovals(key, uow, false); | |
} | |
super.recordPrivateOwnedRemovals(((Map.Entry)object).getValue(), referenceDescriptor, uow); | |
} | |
/** | |
* INTERNAL: | |
* Returns whether this ContainerPolicy requires data modification events when | |
* objects are added or deleted during update. | |
*/ | |
@Override | |
public boolean requiresDataModificationEvents(){ | |
return keyMapping.requiresDataModificationEventsForMapKey(); | |
} | |
/** | |
* INTERNAL: | |
* Return the key for the specified element. | |
*/ | |
@Override | |
public Object keyFrom(Object element, AbstractSession session) { | |
// key is mapped to the database table and not the object and therefore cannot be extracted from the object | |
if (keyMapping != null){ | |
return null; | |
} | |
return super.keyFrom(element, session); | |
} | |
/** | |
* INTERNAL: | |
* Some subclasses need to post initialize mappings associated with them. | |
*/ | |
@Override | |
public void postInitialize(AbstractSession session) { | |
((DatabaseMapping)this.keyMapping).postInitialize(session); | |
this.keyMapping.postInitializeMapKey(this); | |
} | |
/** | |
* INTERNAL: | |
* Propagate the postDeleteEvent to any additional objects the query is aware of | |
*/ | |
@Override | |
public void propogatePostDelete(DeleteObjectQuery query, Object object) { | |
if (propagatesEventsToCollection()){ | |
((AggregateObjectMapping)keyMapping).postDeleteAttributeValue(query, ((Map.Entry)object).getKey()); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Propagate the postDeleteEvent to any additional objects the query is aware of | |
*/ | |
@Override | |
public void propogatePostInsert(WriteObjectQuery query, Object object) { | |
if (propagatesEventsToCollection()){ | |
((AggregateObjectMapping)keyMapping).postInsertAttributeValue(query, ((Map.Entry)object).getKey()); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Propagate the postDeleteEvent to any additional objects the query is aware of | |
*/ | |
@Override | |
public void propogatePostUpdate(WriteObjectQuery query, Object object) { | |
if (propagatesEventsToCollection()){ | |
Object key = object; | |
if (object instanceof Map.Entry){ | |
key = ((Map.Entry)object).getKey(); | |
} | |
((AggregateObjectMapping)keyMapping).postUpdateAttributeValue(query, key); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Propagate the postDeleteEvent to any additional objects the query is aware of | |
*/ | |
@Override | |
public void propogatePreDelete(DeleteObjectQuery query, Object object) { | |
if (propagatesEventsToCollection()){ | |
((AggregateObjectMapping)keyMapping).preDeleteAttributeValue(query, ((Map.Entry)object).getKey()); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Propagate the postDeleteEvent to any additional objects the query is aware of | |
*/ | |
public void propogatePreInsert(WriteObjectQuery query, Object object) { | |
if (propagatesEventsToCollection()){ | |
((AggregateObjectMapping)keyMapping).preInsertAttributeValue(query, ((Map.Entry)object).getKey()); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Propagate the postDeleteEvent to any additional objects the query is aware of | |
*/ | |
@Override | |
public void propogatePreUpdate(WriteObjectQuery query, Object object) { | |
if (propagatesEventsToCollection()) { | |
((AggregateObjectMapping)keyMapping).preUpdateAttributeValue(query, ((Map.Entry)object).getKey()); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Returns true if the key mapping is an AggregateObjectMapping. | |
* Aggregates need events propagated to them because they are not explicitly | |
* deleted, updated or inserted | |
*/ | |
public boolean propagatesEventsToCollection() { | |
return ((DatabaseMapping)keyMapping).isAggregateObjectMapping(); | |
} | |
/** | |
* INTERNAL: | |
* Set the DatabaseField that will represent the key in a DirectMapMapping. | |
*/ | |
public void setKeyField(DatabaseField keyField, ClassDescriptor descriptor) { | |
if (keyMapping == null) { | |
AbstractDirectMapping newKeyMapping = new DirectToFieldMapping(); | |
newKeyMapping.setField(keyField); | |
newKeyMapping.setDescriptor(descriptor); | |
setKeyMapping(newKeyMapping); | |
} | |
if (((DatabaseMapping)keyMapping).isDirectToFieldMapping()) { | |
((AbstractDirectMapping)keyMapping).setField(keyField);; | |
} | |
} | |
/** | |
* INTERNAL: | |
* Used during initialization of DirectMapMapping. Sets the descriptor associated with | |
* the key. | |
*/ | |
public void setDescriptorForKeyMapping(ClassDescriptor descriptor){ | |
((DatabaseMapping)keyMapping).setDescriptor(descriptor); | |
} | |
/** | |
* INTERNAL: | |
* Set a converter on the KeyField of a DirectCollectionMapping. | |
*/ | |
public void setKeyConverter(Converter keyConverter, DirectMapMapping mapping){ | |
if (((DatabaseMapping)keyMapping).isDirectToFieldMapping()){ | |
((AbstractDirectMapping)keyMapping).setConverter(keyConverter); | |
} else { | |
throw DescriptorException.cannotSetConverterForNonDirectMapping(mapping.getDescriptor(), mapping, keyConverter.getClass().getName()); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Set the name of the class to be used as a converter for the key of a DirectMapMaping. | |
*/ | |
public void setKeyConverterClassName(String keyConverterClassName, DirectMapMapping mapping){ | |
if (((DatabaseMapping)keyMapping).isDirectToFieldMapping()){ | |
((AbstractDirectMapping)keyMapping).setConverterClassName(keyConverterClassName); | |
} else { | |
throw DescriptorException.cannotSetConverterForNonDirectMapping(mapping.getDescriptor(), mapping, keyConverterClassName); | |
} | |
} | |
public void setKeyMapping(MapKeyMapping mapping){ | |
if (((DatabaseMapping)mapping).isForeignReferenceMapping() && ((ForeignReferenceMapping)mapping).getIndirectionPolicy().usesIndirection()){ | |
throw ValidationException.mapKeyCannotUseIndirection((DatabaseMapping)mapping); | |
} | |
this.keyMapping = mapping; | |
((DatabaseMapping)mapping).setIsMapKeyMapping(true); | |
} | |
/** | |
* INTERNAL: | |
* Some map keys must be obtained from the database. This query is used to obtain the key | |
* @param keyQuery | |
*/ | |
public void setKeyQuery(DatabaseQuery keyQuery) { | |
this.keyQuery = keyQuery; | |
} | |
public void setValueMapping(MapComponentMapping mapping) { | |
this.valueMapping = mapping; | |
} | |
/** | |
* INTERNAL: | |
* Return whether data for a map key must be included on a Delete datamodification event | |
* If the keyMapping is privateOwned, that data should be. | |
*/ | |
@Override | |
public boolean shouldIncludeKeyInDeleteEvent() { | |
return ((DatabaseMapping)keyMapping).isPrivateOwned(); | |
} | |
/** | |
* INTERNAL: | |
* Certain types of container policies require an extra update statement after a relationship | |
* is inserted. Return whether this update statement is required. | |
*/ | |
@Override | |
public boolean shouldUpdateForeignKeysPostInsert() { | |
return !((DatabaseMapping)keyMapping).isReadOnly(); | |
} | |
/** | |
* INTERNAL: | |
* Update the joined mapping indices | |
* Adds the key mapping and it's index to the list of joined mappings. | |
*/ | |
@Override | |
public int updateJoinedMappingIndexesForMapKey(Map<DatabaseMapping, Object> indexList, int index){ | |
indexList.put((DatabaseMapping)keyMapping, index); | |
return getAdditionalFieldsForJoin(null).size(); | |
} | |
/** | |
* INTERNAL: | |
* Allow the key to be unwrapped. This will be overridden by container policies that | |
* allow keys that are entities. | |
*/ | |
@Override | |
public Object unwrapKey(Object key, AbstractSession session){ | |
return keyMapping.unwrapKey(key, session); | |
} | |
/** | |
* INTERNAL: | |
* This method is used to load a relationship from a list of PKs. This list | |
* may be available if the relationship has been cached. | |
*/ | |
@Override | |
public Object valueFromPKList(Object[] pks, AbstractRecord foreignKeys, ForeignReferenceMapping mapping, AbstractSession session){ | |
int mapSize = pks.length/2; | |
Object result = containerInstance(mapSize); | |
Object[] keys = new Object[mapSize]; | |
Object[] values = new Object[mapSize]; | |
for (int index = 0; index < pks.length; ++index){ | |
keys[index/2] = pks[index]; | |
++index; | |
values[index/2] = pks[index]; | |
} | |
List<Object> keyObjects = keyMapping.createMapComponentsFromSerializableKeyInfo(keys, session); | |
if (((DatabaseMapping)valueMapping).isElementCollectionMapping()) { | |
for(int i = 0; i < mapSize; i++){ | |
addInto(keyObjects.get(i), values[i], result, session); | |
} | |
} else { | |
Map<Object, Object> fromCache = session.getIdentityMapAccessorInstance().getAllFromIdentityMapWithEntityPK(values, elementDescriptor); | |
List foreignKeyValues = new ArrayList(pks.length - fromCache.size()); | |
CacheKeyType cacheKeyType = this.elementDescriptor.getCachePolicy().getCacheKeyType(); | |
for (int index = 0; index < mapSize; ++index){ | |
Object pk = values[index]; | |
if (!fromCache.containsKey(pk)){ | |
if (cacheKeyType == CacheKeyType.CACHE_ID){ | |
foreignKeyValues.add(Arrays.asList(((CacheId)pk).getPrimaryKey())); | |
}else{ | |
foreignKeyValues.add(pk); | |
} | |
} | |
} | |
if (!foreignKeyValues.isEmpty()){ | |
if (foreignKeyValues.size() == pks.length){ | |
//need to find all of the entities so just perform a FK search | |
return session.executeQuery(mapping.getSelectionQuery(), foreignKeys); | |
} | |
ReadAllQuery query = new ReadAllQuery(elementDescriptor.getJavaClass()); | |
query.setIsExecutionClone(true); | |
query.addArgument(ForeignReferenceMapping.QUERY_BATCH_PARAMETER); | |
query.setSession(session); | |
query.setSelectionCriteria(elementDescriptor.buildBatchCriteriaByPK(query.getExpressionBuilder(), query)); | |
int pkCount = foreignKeyValues.size(); | |
Collection<Object> temp = new ArrayList<Object>(); | |
List arguments = new ArrayList(); | |
arguments.add(foreignKeyValues); | |
if (pkCount > 1000){ | |
int index = 0; | |
while ( index+1000 < pkCount ) { // some databases only support ins < 1000 entries | |
List pkList = new ArrayList(); | |
pkList.addAll(foreignKeyValues.subList(index, index+1000)); | |
arguments.set(0, pkList); | |
query.setArgumentValues(arguments); | |
temp.addAll((Collection<Object>) session.executeQuery(query)); | |
index += 1000; | |
} | |
foreignKeyValues = foreignKeyValues.subList(index, pkCount); | |
} | |
arguments.set(0, foreignKeyValues); | |
query.setArgumentValues(arguments); | |
//need to put the translation row here or it will be replaced later. | |
temp.addAll((Collection<Object>) session.executeQuery(query)); | |
if (temp.size() < pkCount){ | |
//Not enough results have been found, this must be a stale collection with a removed | |
//element. Execute a reload based on FK. | |
return session.executeQuery(mapping.getSelectionQuery(), foreignKeys); | |
} | |
for (Object element: temp){ | |
Object pk = elementDescriptor.getObjectBuilder().extractPrimaryKeyFromObject(element, session); | |
fromCache.put(pk, element); | |
} | |
} | |
Iterator keyIterator = keyObjects.iterator(); | |
for(Object key : values){ | |
addInto(keyIterator.next(), fromCache.get(key), result, session); | |
} | |
} | |
return result; | |
} | |
} |