blob: 35c06f37f6b41a0b39a67cd65ff9efe987ad8771 [file] [log] [blame]
/*
* 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 keyIterator = keyObjects.iterator();
for(Object key : values){
addInto(keyIterator.next(), fromCache.get(key), result, session);
}
}
return result;
}
}