| /* |
| * 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: |
| // 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.ObjectReferenceMapping; |
| import org.eclipse.persistence.mappings.converters.Converter; |
| import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping; |
| import org.eclipse.persistence.mappings.foundation.MapComponentMapping; |
| import org.eclipse.persistence.mappings.foundation.MapKeyMapping; |
| 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; |
| |
| /** |
| * 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(Object, Object, AbstractSession) |
| * @see ContainerPolicy#buildReferencesPKList(Object, AbstractSession) |
| */ |
| @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 |
| */ |
| @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 |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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 |
| */ |
| 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<>(); |
| 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<Object> keyIterator = keyObjects.iterator(); |
| for(Object key : values){ |
| addInto(keyIterator.next(), fromCache.get(key), result, session); |
| } |
| } |
| return result; |
| } |
| |
| } |