/******************************************************************************* | |
* 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: | |
* Oracle - initial API and implementation from Oracle TopLink | |
* 07/16/2009-2.0 Guy Pelletier | |
* - 277039: JPA 2.0 Cache Usage Settings | |
* 04/01/2011-2.3 Guy Pelletier | |
* - 337323: Multi-tenant with shared schema support (part 2) | |
* 09/09/2011-2.3.1 Guy Pelletier | |
* - 356197: Add new VPD type to MultitenantType | |
* 11/10/2011-2.4 Guy Pelletier | |
* - 357474: Address primaryKey option from tenant discriminator column | |
******************************************************************************/ | |
package org.eclipse.persistence.internal.descriptors; | |
import java.io.*; | |
import java.util.*; | |
import java.sql.ResultSet; | |
import java.sql.ResultSetMetaData; | |
import java.sql.SQLException; | |
import org.eclipse.persistence.annotations.BatchFetchType; | |
import org.eclipse.persistence.annotations.CacheKeyType; | |
import org.eclipse.persistence.annotations.IdValidation; | |
import org.eclipse.persistence.descriptors.CachePolicy; | |
import org.eclipse.persistence.descriptors.ClassDescriptor; | |
import org.eclipse.persistence.descriptors.DescriptorEvent; | |
import org.eclipse.persistence.descriptors.DescriptorEventManager; | |
import org.eclipse.persistence.descriptors.FetchGroupManager; | |
import org.eclipse.persistence.descriptors.InheritancePolicy; | |
import org.eclipse.persistence.descriptors.changetracking.ChangeTracker; | |
import org.eclipse.persistence.descriptors.changetracking.ObjectChangePolicy; | |
import org.eclipse.persistence.exceptions.*; | |
import org.eclipse.persistence.expressions.*; | |
import org.eclipse.persistence.indirection.ValueHolderInterface; | |
import org.eclipse.persistence.internal.core.descriptors.CoreObjectBuilder; | |
import org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor; | |
import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform; | |
import org.eclipse.persistence.internal.databaseaccess.Platform; | |
import org.eclipse.persistence.internal.expressions.*; | |
import org.eclipse.persistence.internal.helper.*; | |
import org.eclipse.persistence.internal.identitymaps.*; | |
import org.eclipse.persistence.internal.indirection.ProxyIndirectionPolicy; | |
import org.eclipse.persistence.internal.queries.AttributeItem; | |
import org.eclipse.persistence.internal.queries.ContainerPolicy; | |
import org.eclipse.persistence.internal.queries.EntityFetchGroup; | |
import org.eclipse.persistence.internal.queries.JoinedAttributeManager; | |
import org.eclipse.persistence.internal.sessions.*; | |
import org.eclipse.persistence.logging.SessionLog; | |
import org.eclipse.persistence.mappings.*; | |
import org.eclipse.persistence.mappings.DatabaseMapping.WriteType; | |
import org.eclipse.persistence.mappings.foundation.*; | |
import org.eclipse.persistence.queries.*; | |
import org.eclipse.persistence.mappings.querykeys.*; | |
import org.eclipse.persistence.sessions.remote.*; | |
import org.eclipse.persistence.sessions.CopyGroup; | |
import org.eclipse.persistence.sessions.SessionProfiler; | |
import org.eclipse.persistence.sessions.DatabaseRecord; | |
/** | |
* <p><b>Purpose</b>: Object builder is one of the behavior class attached to descriptor. | |
* It is responsible for building objects, rows, and extracting primary keys from | |
* the object and the rows. | |
* | |
* @author Sati | |
* @since TOPLink/Java 1.0 | |
*/ | |
public class ObjectBuilder extends CoreObjectBuilder<AbstractRecord, AbstractSession, DatabaseField, DatabaseMapping> implements Cloneable, Serializable { | |
protected ClassDescriptor descriptor; | |
/** Mappings keyed by attribute name. */ | |
protected Map<String, DatabaseMapping> mappingsByAttribute; | |
/** Mappings keyed by database field. */ | |
protected Map<DatabaseField, DatabaseMapping> mappingsByField; | |
/** List of read-only mappings using a database field. */ | |
protected Map<DatabaseField, List<DatabaseMapping>> readOnlyMappingsByField; | |
/** Used to maintain identity on the field objects. Ensure they get the correct index/type. */ | |
protected Map<DatabaseField, DatabaseField> fieldsMap; | |
/** Mapping for the primary key fields. */ | |
protected List<DatabaseMapping> primaryKeyMappings; | |
/** The types for the primary key fields, in same order as descriptor's primary key fields. */ | |
protected List<Class> primaryKeyClassifications; | |
/** All mapping other than primary key mappings. */ | |
protected transient List<DatabaseMapping> nonPrimaryKeyMappings; | |
/** Expression for querying an object by primary key. */ | |
protected transient Expression primaryKeyExpression; | |
/** PERF: Cache mapping that use joining. */ | |
protected List<DatabaseMapping> joinedAttributes; | |
/** PERF: Cache mapping that use batch fetching. */ | |
protected List<DatabaseMapping> batchFetchedAttributes; | |
/** PERF: Cache mapping that use batch fetching. */ | |
protected boolean hasInBatchFetchedAttribute; | |
/** PERF: Cache mappings that require cloning. */ | |
protected List<DatabaseMapping> cloningMappings; | |
/** PERF: Cache mappings that are eager loaded. */ | |
protected List<DatabaseMapping> eagerMappings; | |
/** PERF: Cache relationship mappings. */ | |
protected List<DatabaseMapping> relationshipMappings; | |
/** PERF: Cache if is a simple mapping, all direct. */ | |
protected boolean isSimple; | |
/** PERF: Cache if has a wrapper policy. */ | |
protected boolean hasWrapperPolicy; | |
/** PERF: Cache sequence mappings. */ | |
protected AbstractDirectMapping sequenceMapping; | |
/** indicates whether part of primary key is unmapped - may happen only in case AggregateObject or AggregateCollection descriptor. */ | |
protected boolean mayHaveNullInPrimaryKey; | |
/** attribute name corresponding to optimistic lock field, set only if optimistic locking is used */ | |
protected String lockAttribute; | |
/** PERF: is there a mapping using indirection (could be nested in aggregate(s)), or any other reason to keep row after the object has been created. | |
Used by ObjectLevelReadQuery ResultSetAccessOptimization. */ | |
protected boolean shouldKeepRow = false; | |
/** PERF: is there an cache index field that's would not be selected by SOP query. Ignored unless descriptor uses SOP and CachePolicy has cache indexes. */ | |
protected boolean hasCacheIndexesInSopObject = false; | |
public ObjectBuilder(ClassDescriptor descriptor) { | |
this.descriptor = descriptor; | |
initialize(descriptor); | |
} | |
protected void initialize(ClassDescriptor descriptor) { | |
this.mappingsByField = new HashMap(20); | |
this.readOnlyMappingsByField = new HashMap(10); | |
this.mappingsByAttribute = new HashMap(20); | |
this.fieldsMap = new HashMap(20); | |
this.primaryKeyMappings = new ArrayList(5); | |
this.nonPrimaryKeyMappings = new ArrayList(10); | |
this.cloningMappings = new ArrayList(10); | |
this.eagerMappings = new ArrayList(5); | |
this.relationshipMappings = new ArrayList(5); | |
} | |
/** | |
* Create a new row/record for the object builder. | |
* This allows subclasses to define different record types. | |
*/ | |
public AbstractRecord createRecord(AbstractSession session) { | |
return new DatabaseRecord(); | |
} | |
/** | |
* Create a new row/record for the object builder. | |
* This allows subclasses to define different record types. | |
*/ | |
public AbstractRecord createRecord(int size, AbstractSession session) { | |
return new DatabaseRecord(size); | |
} | |
/** | |
* Create a new row/record for the object builder. This allows subclasses to | |
* define different record types. This will typically be called when a | |
* record will be used for temporarily holding on to primary key fields. | |
*/ | |
protected AbstractRecord createRecordForPKExtraction(int size, AbstractSession session) { | |
return createRecord(size, session); | |
} | |
/** | |
* Add the primary key and its value to the Record for all the non default tables. | |
* This method is used while writing into the multiple tables. | |
*/ | |
public void addPrimaryKeyForNonDefaultTable(AbstractRecord databaseRow) { | |
// this method has been revised so it calls addPrimaryKeyForNonDefaultTable(AbstractRecord, Object, Session) is similar. | |
// the session and object are null in this case. | |
addPrimaryKeyForNonDefaultTable(databaseRow, null, null); | |
} | |
/** | |
* Add the primary key and its value to the Record for all the non default tables. | |
* This method is used while writing into the multiple tables. | |
*/ | |
public void addPrimaryKeyForNonDefaultTable(AbstractRecord databaseRow, Object object, AbstractSession session) { | |
if (!this.descriptor.hasMultipleTables()) { | |
return; | |
} | |
List<DatabaseTable> tables = this.descriptor.getTables(); | |
int size = tables.size(); | |
// Skip first table. | |
for (int index = 1; index < size; index++) { | |
DatabaseTable table = tables.get(index); | |
Map keyMapping = this.descriptor.getAdditionalTablePrimaryKeyFields().get(table); | |
// Loop over the additionalTablePK fields and add the PK info for the table. The join might | |
// be between a fk in the source table and pk in secondary table. | |
if (keyMapping != null) { | |
Iterator primaryKeyFieldEnum = keyMapping.keySet().iterator(); | |
Iterator secondaryKeyFieldEnum = keyMapping.values().iterator(); | |
while (primaryKeyFieldEnum.hasNext()) { | |
DatabaseField primaryKeyField = (DatabaseField)primaryKeyFieldEnum.next(); | |
DatabaseField secondaryKeyField = (DatabaseField)secondaryKeyFieldEnum.next(); | |
Object primaryValue = databaseRow.getIndicatingNoEntry(primaryKeyField); | |
// normally the primary key has a value, however if the multiple tables were joined by a foreign | |
// key the foreign key has a value. | |
if ((primaryValue == AbstractRecord.noEntry)) { | |
if (object != null) { | |
DatabaseMapping mapping = getMappingForField(secondaryKeyField); | |
if (mapping == null) { | |
throw DescriptorException.missingMappingForField(secondaryKeyField, this.descriptor); | |
} | |
mapping.writeFromObjectIntoRow(object, databaseRow, session, WriteType.UNDEFINED); | |
} | |
databaseRow.put(primaryKeyField, databaseRow.get(secondaryKeyField)); | |
} else { | |
databaseRow.put(secondaryKeyField, primaryValue); | |
} | |
} | |
} | |
} | |
} | |
/** | |
* Clear any primary key cache data in the object. | |
*/ | |
public void clearPrimaryKey(Object object) { | |
// PERF: If PersistenceEntity is caching the primary key this must be cleared as the primary key has changed. | |
if (object instanceof PersistenceEntity) { | |
((PersistenceEntity)object)._persistence_setId(null); | |
} | |
} | |
/** | |
* Assign the fields in the row back into the object. | |
* This is used by returning, as well as events and version locking. | |
* If not null changeSet must correspond to object. changeSet is updated with all of the field values in the row. | |
*/ | |
public void assignReturnRow(Object object, AbstractSession writeSession, AbstractRecord row, ObjectChangeSet changeSet) throws DatabaseException { | |
writeSession.log(SessionLog.FINEST, SessionLog.QUERY, "assign_return_row", row); | |
// Require a query context to read into an object. | |
ReadObjectQuery query = new ReadObjectQuery(); | |
query.setSession(writeSession); | |
// To avoid processing the same mapping twice, | |
// maintain Collection of mappings already used. | |
HashSet handledMappings = null; | |
int size = row.size(); | |
if (size > 1) { | |
handledMappings = new HashSet(size); | |
} | |
List fields = row.getFields(); | |
for (int index = 0; index < size; index++) { | |
DatabaseField field = (DatabaseField)fields.get(index); | |
assignReturnValueForField(object, query, row, field, handledMappings, changeSet); | |
} | |
} | |
/** | |
* Assign the field value from the row to the object for all the mappings using field (read or write). | |
* If not null changeSet must correspond to object. changeSet is updated with all of the field values in the row. | |
*/ | |
public void assignReturnValueForField(Object object, ReadObjectQuery query, AbstractRecord row, DatabaseField field, Collection handledMappings, ObjectChangeSet changeSet) { | |
DatabaseMapping mapping = getMappingForField(field); | |
if (mapping != null) { | |
assignReturnValueToMapping(object, query, row, field, mapping, handledMappings, changeSet); | |
} | |
List readOnlyMappings = getReadOnlyMappingsForField(field); | |
if (readOnlyMappings != null) { | |
int size = readOnlyMappings.size(); | |
for (int index = 0; index < size; index++) { | |
mapping = (DatabaseMapping)readOnlyMappings.get(index); | |
assignReturnValueToMapping(object, query, row, field, mapping, handledMappings, changeSet); | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* Assign values from objectRow to the object through the mapping. | |
* If not null changeSet must correspond to object. changeSet is updated with all of the field values in the row. | |
*/ | |
protected void assignReturnValueToMapping(Object object, ReadObjectQuery query, AbstractRecord row, DatabaseField field, DatabaseMapping mapping, Collection handledMappings, ObjectChangeSet changeSet) { | |
if ((handledMappings != null) && handledMappings.contains(mapping)) { | |
return; | |
} | |
if (mapping.isAbstractDirectMapping()) { | |
if(changeSet != null && (!changeSet.isNew() || (query.getDescriptor() != null && query.getDescriptor().shouldUseFullChangeSetsForNewObjects()))) { | |
DirectToFieldChangeRecord changeRecord = (DirectToFieldChangeRecord)changeSet.getChangesForAttributeNamed(mapping.getAttributeName()); | |
Object oldAttributeValue = null; | |
if (changeRecord == null) { | |
oldAttributeValue = mapping.getAttributeValueFromObject(object); | |
} | |
//use null cachekey to ensure we build directly into the attribute | |
Object attributeValue = mapping.readFromRowIntoObject(row, null, object, null, query, query.getSession(), true); | |
if (changeRecord == null) { | |
// Don't use ObjectChangeSet.updateChangeRecordForAttributeWithMappedObject to avoid unnecessary conversion - attributeValue is already converted. | |
changeRecord = (DirectToFieldChangeRecord)((AbstractDirectMapping)mapping).internalBuildChangeRecord(attributeValue, oldAttributeValue, changeSet); | |
changeSet.addChange(changeRecord); | |
} else { | |
changeRecord.setNewValue(attributeValue); | |
} | |
} else { | |
mapping.readFromRowIntoObject(row, null, object, null, query, query.getSession(), true); | |
} | |
} else if (mapping.isAggregateObjectMapping()) { | |
((AggregateObjectMapping)mapping).readFromReturnRowIntoObject(row, object, query, handledMappings, changeSet); | |
} else if (mapping.isTransformationMapping()) { | |
((AbstractTransformationMapping)mapping).readFromReturnRowIntoObject(row, object, query, handledMappings, changeSet); | |
} else { | |
query.getSession().log(SessionLog.FINEST, SessionLog.QUERY, "field_for_unsupported_mapping_returned", field, this.descriptor); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Update the object primary key by fetching a new sequence number from the accessor. | |
* This assume the uses sequence numbers check has already been done. | |
* @return the sequence value or null if not assigned. | |
* @exception DatabaseException - an error has occurred on the database. | |
*/ | |
public Object assignSequenceNumber(Object object, AbstractSession writeSession) throws DatabaseException { | |
return assignSequenceNumber(object, writeSession, null); | |
} | |
/** | |
* INTERNAL: | |
* Update the writeQuery's object primary key by fetching a new sequence number from the accessor. | |
* This assume the uses sequence numbers check has already been done. | |
* Adds the assigned sequence value to writeQuery's modify row. | |
* If object has a changeSet then sets sequence value into change set as an Id | |
* adds it also to object's change set in a ChangeRecord if required. | |
* @return the sequence value or null if not assigned. | |
* @exception DatabaseException - an error has occurred on the database. | |
*/ | |
public Object assignSequenceNumber(WriteObjectQuery writeQuery) throws DatabaseException { | |
return assignSequenceNumber(writeQuery.getObject(), writeQuery.getSession(), writeQuery); | |
} | |
/** | |
* INTERNAL: | |
* Update the object primary key by fetching a new sequence number from the accessor. | |
* This assume the uses sequence numbers check has already been done. | |
* Adds the assigned sequence value to writeQuery's modify row. | |
* If object has a changeSet then sets sequence value into change set as an Id | |
* adds it also to object's change set in a ChangeRecord if required. | |
* @return the sequence value or null if not assigned. | |
* @exception DatabaseException - an error has occurred on the database. | |
*/ | |
protected Object assignSequenceNumber(Object object, AbstractSession writeSession, WriteObjectQuery writeQuery) throws DatabaseException { | |
DatabaseField sequenceNumberField = this.descriptor.getSequenceNumberField(); | |
Object existingValue = null; | |
if (this.sequenceMapping != null) { | |
existingValue = this.sequenceMapping.getAttributeValueFromObject(object); | |
} else { | |
existingValue = getBaseValueForField(sequenceNumberField, object); | |
} | |
// PERF: The (internal) support for letting the sequence decide this was removed, | |
// as anything other than primitive should allow null and default as such. | |
Object sequenceValue; | |
int index = this.descriptor.getPrimaryKeyFields().indexOf(sequenceNumberField); | |
if (isPrimaryKeyComponentInvalid(existingValue, index) || this.descriptor.getSequence().shouldAlwaysOverrideExistingValue()) { | |
sequenceValue = writeSession.getSequencing().getNextValue(this.descriptor.getJavaClass()); | |
} else { | |
return null; | |
} | |
// Check that the value is not null, this occurs on any databases using IDENTITY type sequencing. | |
if (sequenceValue == null) { | |
return null; | |
} | |
writeSession.log(SessionLog.FINEST, SessionLog.SEQUENCING, "assign_sequence", sequenceValue, object); | |
Object convertedSequenceValue = null; | |
if (this.sequenceMapping != null) { | |
convertedSequenceValue = this.sequenceMapping.getObjectValue(sequenceValue, writeSession); | |
this.sequenceMapping.setAttributeValueInObject(object, convertedSequenceValue); | |
} else { | |
// Now add the value to the object, this gets ugly. | |
AbstractRecord tempRow = createRecord(1, writeSession); | |
tempRow.put(sequenceNumberField, sequenceValue); | |
// Require a query context to read into an object. | |
ReadObjectQuery query = new ReadObjectQuery(); | |
query.setSession(writeSession); | |
DatabaseMapping mapping = getBaseMappingForField(sequenceNumberField); | |
Object sequenceIntoObject = getParentObjectForField(sequenceNumberField, object); | |
// The following method will return the converted value for the sequence. | |
convertedSequenceValue = mapping.readFromRowIntoObject(tempRow, null, sequenceIntoObject, null, query, writeSession, true); | |
} | |
// PERF: If PersistenceEntity is caching the primary key this must be cleared as the primary key has changed. | |
clearPrimaryKey(object); | |
if (writeQuery != null) { | |
Object primaryKey = extractPrimaryKeyFromObject(object, writeSession); | |
writeQuery.setPrimaryKey(primaryKey); | |
AbstractRecord modifyRow = writeQuery.getModifyRow(); | |
// Update the row. | |
modifyRow.put(sequenceNumberField, sequenceValue); | |
if (descriptor.hasMultipleTables()) { | |
addPrimaryKeyForNonDefaultTable(modifyRow, object, writeSession); | |
} | |
// Update the changeSet if there is one. | |
if (writeSession.isUnitOfWork()) { | |
ObjectChangeSet objectChangeSet = writeQuery.getObjectChangeSet(); | |
if ((objectChangeSet == null) && (((UnitOfWorkImpl)writeSession).getUnitOfWorkChangeSet() != null)) { | |
objectChangeSet = (ObjectChangeSet)((UnitOfWorkImpl)writeSession).getUnitOfWorkChangeSet().getObjectChangeSetForClone(object); | |
} | |
if (objectChangeSet != null) { | |
// objectChangeSet.isNew() == true | |
if (writeQuery.getDescriptor().shouldUseFullChangeSetsForNewObjects()) { | |
if (this.sequenceMapping != null) { | |
// Don't use ObjectChangeSet.updateChangeRecordForAttribute to avoid unnecessary conversion - convertedSequenceValue is already converted. | |
String attributeName = this.sequenceMapping.getAttributeName(); | |
DirectToFieldChangeRecord changeRecord = (DirectToFieldChangeRecord)objectChangeSet.getChangesForAttributeNamed(attributeName); | |
if (changeRecord == null) { | |
changeRecord = new DirectToFieldChangeRecord(objectChangeSet); | |
changeRecord.setAttribute(attributeName); | |
changeRecord.setMapping(this.sequenceMapping); | |
objectChangeSet.addChange(changeRecord); | |
} | |
changeRecord.setNewValue(convertedSequenceValue); | |
} else { | |
ChangeRecord changeRecord = getBaseChangeRecordForField(objectChangeSet, object, sequenceNumberField, writeSession); | |
if (changeRecord.getMapping().isDirectCollectionMapping()) { | |
// assign converted value to the attribute | |
((DirectToFieldChangeRecord)changeRecord).setNewValue(convertedSequenceValue); | |
} else if (changeRecord.getMapping().isTransformationMapping()) { | |
// put original (not converted) value into the record. | |
((TransformationMappingChangeRecord)changeRecord).getRecord().put(sequenceNumberField, sequenceValue); | |
} | |
} | |
} | |
objectChangeSet.setId(primaryKey); | |
} | |
} | |
} | |
return convertedSequenceValue; | |
} | |
/** | |
* Each mapping is recursed to assign values from the Record to the attributes in the domain object. | |
*/ | |
public void buildAttributesIntoObject(Object domainObject, CacheKey cacheKey, AbstractRecord databaseRow, ObjectBuildingQuery query, JoinedAttributeManager joinManager, FetchGroup executionFetchGroup, boolean forRefresh, AbstractSession targetSession) throws DatabaseException { | |
if (this.descriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy()) { | |
if (buildAttributesIntoObjectSOP(domainObject, cacheKey, databaseRow, query, joinManager, executionFetchGroup, forRefresh, targetSession)) { | |
return; | |
} | |
} | |
// PERF: Avoid synchronized enumerator as is concurrency bottleneck. | |
List mappings = this.descriptor.getMappings(); | |
// PERF: Cache if all mappings should be read. | |
boolean readAllMappings = query.shouldReadAllMappings(); | |
boolean isTargetProtected = targetSession.isProtectedSession(); | |
int size = mappings.size(); | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); | |
if (readAllMappings || query.shouldReadMapping(mapping, executionFetchGroup)) { | |
mapping.readFromRowIntoObject(databaseRow, joinManager, domainObject, cacheKey, query, targetSession, isTargetProtected); | |
} | |
} | |
// PERF: Avoid events if no listeners. | |
if (this.descriptor.hasEventManager()) { | |
postBuildAttributesIntoObjectEvent(domainObject, databaseRow, query, forRefresh); | |
} | |
} | |
/** | |
* Each mapping is recursed to assign values from the Record to the attributes in the domain object. | |
* Should not be called unless (this.descriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy()) | |
* This method populates the object only in if some mappings potentially should be read using sopObject and other mappings - not using it. | |
* That happens when the row has been just read from the database and potentially has serialized object still in deserialized bits as a field value. | |
* Note that domainObject == sopObject is the same case, but (because domainObject has to be set into cache beforehand) extraction of sopObject | |
* from bit was done right before this method is called. | |
* Alternative situation is processing an empty row that has been created by foreign reference mapping | |
* and holds nothing but sopObject (which is an attribute of the original sopObject) - this case falls through to buildAttributesIntoObject. | |
* If attempt to deserialize sopObject from bits has failed, but SOP was setup to allow recovery | |
* (all mapped all fields/value mapped to the object were read, not just those excluded from SOP) | |
* then fall through to buildAttributesIntoObject. | |
* Nothing should be done if sopObject is not null, but domainObject != sopObject: | |
* the only way to get into this case should be with original query not maintaining cache, | |
* through a back reference to the original object, which is already being built (or has been built). | |
* @return whether the object has been populated with attributes, if not then buildAttributesIntoObject should be called. | |
*/ | |
protected boolean buildAttributesIntoObjectSOP(Object domainObject, CacheKey cacheKey, AbstractRecord databaseRow, ObjectBuildingQuery query, JoinedAttributeManager joinManager, FetchGroup executionFetchGroup, boolean forRefresh, AbstractSession targetSession) throws DatabaseException { | |
Object sopObject = databaseRow.getSopObject(); | |
if (domainObject == sopObject) { | |
// PERF: Cache if all mappings should be read. | |
boolean readAllMappings = query.shouldReadAllMappings(); | |
boolean isTargetProtected = targetSession.isProtectedSession(); | |
// domainObject is sopObject | |
for (DatabaseMapping mapping : this.descriptor.getMappings()) { | |
if (readAllMappings || query.shouldReadMapping(mapping, executionFetchGroup)) { | |
// to avoid re-setting the same attribute value to domainObject | |
// only populate if either mapping (possibly nested) may reference entity or mapping does not use sopObject | |
if (mapping.hasNestedIdentityReference() || mapping.isOutOnlySopObject()) { | |
if (mapping.isOutSopObject()) { | |
// the mapping should be processed as if there is no sopObject | |
databaseRow.setSopObject(null); | |
} else { | |
databaseRow.setSopObject(sopObject); | |
} | |
mapping.readFromRowIntoObject(databaseRow, joinManager, domainObject, cacheKey, query, targetSession, isTargetProtected); | |
} | |
} | |
} | |
// PERF: Avoid events if no listeners. | |
if (this.descriptor.hasEventManager()) { | |
postBuildAttributesIntoObjectEvent(domainObject, databaseRow, query, forRefresh); | |
} | |
// sopObject has been processed by all relevant mappings, no longer required. | |
databaseRow.setSopObject(null); | |
return true; | |
} else { | |
if (sopObject == null) { | |
// serialized sopObject is a value corresponding to sopField in the row, row.sopObject==null; | |
// the following line sets deserialized sopObject into row.sopObject variable and sets sopField's value to null; | |
sopObject = this.descriptor.getSerializedObjectPolicy().getObjectFromRow(databaseRow, targetSession, (ObjectLevelReadQuery)query); | |
if (sopObject != null) { | |
// PERF: Cache if all mappings should be read. | |
boolean readAllMappings = query.shouldReadAllMappings(); | |
boolean isTargetProtected = targetSession.isProtectedSession(); | |
for (DatabaseMapping mapping : this.descriptor.getMappings()) { | |
if (readAllMappings || query.shouldReadMapping(mapping, executionFetchGroup)) { | |
if (mapping.isOutSopObject()) { | |
// the mapping should be processed as if there is no sopObject | |
databaseRow.setSopObject(null); | |
} else { | |
databaseRow.setSopObject(sopObject); | |
} | |
mapping.readFromRowIntoObject(databaseRow, joinManager, domainObject, cacheKey, query, targetSession, isTargetProtected); | |
} | |
} | |
// PERF: Avoid events if no listeners. | |
if (this.descriptor.hasEventManager()) { | |
postBuildAttributesIntoObjectEvent(domainObject, databaseRow, query, forRefresh); | |
} | |
// sopObject has been processed by all relevant mappings, no longer required. | |
databaseRow.setSopObject(null); | |
return true; | |
} else { | |
// SOP.getObjectFromRow returned null means serialized bits for sopObject either missing or deserilaized sopObject is invalid. | |
// If the method hasn't thrown exception then populating of the object is possible from the regular fields/values of the row | |
// (that means all fields/value mapped to the object were read, not just those excluded from SOP). | |
// return false and fall through to buildAttributesIntoObject | |
return false; | |
} | |
} else { | |
// A mapping under SOP can't have another SOP on its reference descriptor, | |
// but that's what seem to be happening. | |
// The only way to get here should be with original query not maintaining cache, | |
// through a back reference to the original object, which is already being built (or has been built). | |
// Leave without building. | |
return true; | |
} | |
} | |
} | |
protected void postBuildAttributesIntoObjectEvent(Object domainObject, AbstractRecord databaseRow, ObjectBuildingQuery query, boolean forRefresh) { | |
DescriptorEventManager descriptorEventManager = this.descriptor.getDescriptorEventManager(); | |
if(descriptorEventManager.hasAnyEventListeners()) { | |
// Need to run post build or refresh selector, currently check with the query for this, | |
// I'm not sure which should be called it case of refresh building a new object, currently refresh is used... | |
org.eclipse.persistence.descriptors.DescriptorEvent event = new DescriptorEvent(domainObject); | |
event.setQuery(query); | |
event.setSession(query.getSession()); | |
event.setRecord(databaseRow); | |
if (forRefresh) { | |
//this method can be called from different places within TopLink. We may be | |
//executing refresh query but building the object not refreshing so we must | |
//throw the appropriate event. | |
//bug 3325315 | |
event.setEventCode(DescriptorEventManager.PostRefreshEvent); | |
} else { | |
event.setEventCode(DescriptorEventManager.PostBuildEvent); | |
} | |
descriptorEventManager.executeEvent(event); | |
} | |
} | |
/** | |
* Returns the backup clone of the specified object. This is called only from unit of work. | |
* The clone sent as parameter is always a working copy from the unit of work. | |
*/ | |
public Object buildBackupClone(Object clone, UnitOfWorkImpl unitOfWork) { | |
// The copy policy builds clone . | |
ClassDescriptor descriptor = this.descriptor; | |
Object backup = descriptor.getCopyPolicy().buildClone(clone, unitOfWork); | |
// PERF: Avoid synchronized enumerator as is concurrency bottleneck. | |
List mappings = getCloningMappings(); | |
int size = mappings.size(); | |
if (descriptor.hasFetchGroupManager() && descriptor.getFetchGroupManager().isPartialObject(clone)) { | |
FetchGroupManager fetchGroupManager = descriptor.getFetchGroupManager(); | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); | |
if (fetchGroupManager.isAttributeFetched(clone, mapping.getAttributeName())) { | |
mapping.buildBackupClone(clone, backup, unitOfWork); | |
} | |
} | |
} else { | |
for (int index = 0; index < size; index++) { | |
((DatabaseMapping)mappings.get(index)).buildBackupClone(clone, backup, unitOfWork); | |
} | |
} | |
return backup; | |
} | |
/** | |
* Build and return the expression to use as the where clause to delete an object. | |
* The row is passed to allow the version number to be extracted from it. | |
* If called with usesOptimisticLocking==true the caller should make sure that descriptor uses optimistic locking policy. | |
*/ | |
public Expression buildDeleteExpression(DatabaseTable table, AbstractRecord row, boolean usesOptimisticLocking) { | |
if (usesOptimisticLocking && (this.descriptor.getTables().firstElement().equals(table))) { | |
return this.descriptor.getOptimisticLockingPolicy().buildDeleteExpression(table, primaryKeyExpression, row); | |
} else { | |
return buildPrimaryKeyExpression(table); | |
} | |
} | |
/** | |
* INTERNAL: | |
* This method is used when Query By Example is used. Going through the mappings one by one, this method | |
* calls the specific buildExpression method corresponding to the type of mapping. It then generates a | |
* complete Expression by joining the individual Expressions. | |
*/ | |
public Expression buildExpressionFromExample(Object queryObject, QueryByExamplePolicy policy, Expression expressionBuilder, Map processedObjects, AbstractSession session) { | |
if (processedObjects.containsKey(queryObject)) { | |
//this object has already been queried on | |
return null; | |
} | |
processedObjects.put(queryObject, queryObject); | |
Expression expression = null; | |
// PERF: Avoid synchronized enumerator as is concurrency bottleneck. | |
List mappings = this.descriptor.getMappings(); | |
for (int index = 0; index < mappings.size(); index++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); | |
if (expression == null) { | |
expression = mapping.buildExpression(queryObject, policy, expressionBuilder, processedObjects, session); | |
} else { | |
expression = expression.and(mapping.buildExpression(queryObject, policy, expressionBuilder, processedObjects, session)); | |
} | |
} | |
return expression; | |
} | |
/** | |
* Return a new instance of the receiver's javaClass. | |
*/ | |
public Object buildNewInstance() { | |
return this.descriptor.getInstantiationPolicy().buildNewInstance(); | |
} | |
/** | |
* Return an instance of the receivers javaClass. Set the attributes of an instance | |
* from the values stored in the database row. | |
*/ | |
public Object buildObject(ObjectLevelReadQuery query, AbstractRecord databaseRow) { | |
// PERF: Avoid lazy init of join manager if no joining. | |
JoinedAttributeManager joinManager = null; | |
if (query.hasJoining()) { | |
joinManager = query.getJoinedAttributeManager(); | |
} | |
return buildObject(query, databaseRow, joinManager); | |
} | |
/** | |
* Return an instance of the receivers javaClass. Set the attributes of an instance | |
* from the values stored in the database row. | |
*/ | |
public Object buildObject(ObjectBuildingQuery query, AbstractRecord databaseRow, JoinedAttributeManager joinManager) { | |
InheritancePolicy inheritancePolicy = null; | |
if (this.descriptor.hasInheritance()) { | |
inheritancePolicy = this.descriptor.getInheritancePolicy(); | |
} | |
AbstractSession session = query.getSession(); | |
session.startOperationProfile(SessionProfiler.ObjectBuilding, query, SessionProfiler.ALL); | |
Object domainObject = null; | |
try { | |
domainObject = buildObject(query, databaseRow, joinManager, session, this.descriptor, inheritancePolicy, session.isUnitOfWork(), query.shouldCacheQueryResults(), query.shouldUseWrapperPolicy()); | |
} finally { | |
session.endOperationProfile(SessionProfiler.ObjectBuilding, query, SessionProfiler.ALL); | |
} | |
return domainObject; | |
} | |
/** | |
* Return an instance of the receivers javaClass. Set the attributes of an instance | |
* from the values stored in the database row. | |
*/ | |
public Object buildObject(ObjectBuildingQuery query, AbstractRecord databaseRow, JoinedAttributeManager joinManager, | |
AbstractSession session, ClassDescriptor concreteDescriptor, InheritancePolicy inheritancePolicy, boolean isUnitOfWork, | |
boolean shouldCacheQueryResults, boolean shouldUseWrapperPolicy) { | |
Object domainObject = null; | |
CacheKey prefechedCacheKey = null; | |
Object primaryKey = extractPrimaryKeyFromRow(databaseRow, session); | |
// Check for null primary key, this is not allowed. | |
if ((primaryKey == null) && (!query.hasPartialAttributeExpressions()) && (!this.descriptor.isAggregateCollectionDescriptor())) { | |
//BUG 3168689: EJBQL: "Select Distinct s.customer from SpouseBean s" | |
//BUG 3168699: EJBQL: "Select s.customer from SpouseBean s where s.id = '6'" | |
//If we return either a single null, or a Collection containing at least | |
//one null, then we want the nulls returned/included if the indicated | |
//property is set in the query. (As opposed to throwing an Exception). | |
if (query.shouldBuildNullForNullPk()) { | |
return null; | |
} else { | |
throw QueryException.nullPrimaryKeyInBuildingObject(query, databaseRow); | |
} | |
} | |
if (query.getPrefetchedCacheKeys() != null){ | |
prefechedCacheKey = query.getPrefetchedCacheKeys().get(primaryKey); | |
} | |
if ((inheritancePolicy != null) && inheritancePolicy.shouldReadSubclasses()) { | |
Class classValue = inheritancePolicy.classFromRow(databaseRow, session); | |
concreteDescriptor = inheritancePolicy.getDescriptor(classValue); | |
if ((concreteDescriptor == null) && query.hasPartialAttributeExpressions()) { | |
concreteDescriptor = this.descriptor; | |
} | |
if (concreteDescriptor == null) { | |
throw QueryException.noDescriptorForClassFromInheritancePolicy(query, classValue); | |
} | |
} | |
if (isUnitOfWork) { | |
// Do not wrap yet if in UnitOfWork, as there is still much more | |
// processing ahead. | |
domainObject = buildObjectInUnitOfWork(query, joinManager, databaseRow, (UnitOfWorkImpl)session, primaryKey, prefechedCacheKey, concreteDescriptor); | |
} else { | |
domainObject = buildObject(false, query, databaseRow, session, primaryKey, prefechedCacheKey, concreteDescriptor, joinManager); | |
if (shouldCacheQueryResults) { | |
query.cacheResult(domainObject); | |
} | |
// wrap the object if the query requires it. | |
if (shouldUseWrapperPolicy) { | |
domainObject = concreteDescriptor.getObjectBuilder().wrapObject(domainObject, session); | |
} | |
} | |
return domainObject; | |
} | |
/** | |
* Force instantiation to any eager mappings. | |
*/ | |
public void instantiateEagerMappings(Object object, AbstractSession session) { | |
// Force instantiation to eager mappings. | |
if (!this.eagerMappings.isEmpty()) { | |
FetchGroup fetchGroup = null; | |
FetchGroupManager fetchGroupManager = this.descriptor.getFetchGroupManager(); | |
if (fetchGroupManager != null) { | |
fetchGroup = fetchGroupManager.getObjectFetchGroup(object); | |
} | |
int size = this.eagerMappings.size(); | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = this.eagerMappings.get(index); | |
if (fetchGroup == null || fetchGroup.containsAttributeInternal(mapping.getAttributeName())) { | |
mapping.instantiateAttribute(object, session); | |
} | |
} | |
} | |
} | |
/** | |
* Force instantiation to any mappings in the load group. | |
*/ | |
public void load(final Object object, AttributeGroup group, final AbstractSession session, final boolean fromFetchGroup) { | |
FetchGroupManager fetchGroupManager = this.descriptor.getFetchGroupManager(); | |
if (fetchGroupManager != null) { | |
FetchGroup fetchGroup = fetchGroupManager.getObjectFetchGroup(object); | |
if (fetchGroup != null) { | |
if (!fetchGroup.getAttributeNames().containsAll(group.getAttributeNames())) { | |
// trigger fetch group if it does not contain all attributes of the current group. | |
fetchGroup.onUnfetchedAttribute((FetchGroupTracker)object, null); | |
} | |
} | |
} | |
for (AttributeItem eachItem : group.getAllItems().values()) { | |
final DatabaseMapping mapping = getMappingForAttributeName(eachItem.getAttributeName()); | |
final AttributeItem item = eachItem; | |
if (mapping == null) { | |
// no mapping found | |
throw ValidationException.fetchGroupHasUnmappedAttribute(group, item.getAttributeName()); | |
} | |
// Allow the attributes to be loaded on concurrent threads. | |
// Only do so on a ServerSession, as other sessions are not thread safe. | |
if (group.isConcurrent() && session.isServerSession()) { | |
Runnable runnable = new Runnable() { | |
public void run() { | |
mapping.load(object, item, session, fromFetchGroup); | |
} | |
}; | |
session.getServerPlatform().launchContainerRunnable(runnable); | |
} else { | |
mapping.load(object, item, session, fromFetchGroup); | |
} | |
} | |
} | |
/** | |
* Force instantiation of all indirections. | |
*/ | |
public void loadAll(Object object, AbstractSession session) { | |
loadAll(object, session, new IdentityHashSet()); | |
} | |
public void loadAll(Object object, AbstractSession session, IdentityHashSet loaded) { | |
if (loaded.contains(object)) { | |
return; | |
} | |
loaded.add(object); | |
for (DatabaseMapping mapping : this.descriptor.getMappings()) { | |
mapping.loadAll(object, session, loaded); | |
} | |
} | |
/** | |
* For executing all reads on the UnitOfWork, the session when building | |
* objects from rows will now be the UnitOfWork. Useful if the rows were | |
* read via a dirty write connection and we want to avoid putting uncommitted | |
* data in the global cache. | |
* <p> | |
* Decides whether to call either buildWorkingCopyCloneFromRow (bypassing | |
* shared cache) or buildWorkingCopyCloneNormally (placing the result in the | |
* shared cache). | |
*/ | |
protected Object buildObjectInUnitOfWork(ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, Object primaryKey, CacheKey preFetchedCacheKey, ClassDescriptor concreteDescriptor) throws DatabaseException, QueryException { | |
// When in transaction we are reading via the write connection | |
// and so do not want to corrupt the shared cache with dirty objects. | |
// Hence we build and refresh clones directly from the database row. | |
// PERF: Allow the session cached to still be used after early transaction if isolation setting has been set. | |
CachePolicy cachePolicy = concreteDescriptor.getCachePolicy(); | |
if (!cachePolicy.shouldUseSessionCacheInUnitOfWorkEarlyTransaction()) { | |
if (((unitOfWork.hasCommitManager() && unitOfWork.getCommitManager().isActive()) | |
|| unitOfWork.wasTransactionBegunPrematurely() | |
|| cachePolicy.shouldIsolateObjectsInUnitOfWork() | |
|| cachePolicy.shouldIsolateProtectedObjectsInUnitOfWork() | |
|| query.shouldStoreBypassCache()) | |
&& (!unitOfWork.isClassReadOnly(concreteDescriptor.getJavaClass(), concreteDescriptor))) { | |
// It is easier to switch once to the correct builder here. | |
return concreteDescriptor.getObjectBuilder().buildWorkingCopyCloneFromRow(query, joinManager, databaseRow, unitOfWork, primaryKey, preFetchedCacheKey); | |
} | |
} | |
return buildWorkingCopyCloneNormally(query, databaseRow, unitOfWork, primaryKey, preFetchedCacheKey, concreteDescriptor, joinManager); | |
} | |
/** | |
* buildWorkingCopyCloneFromRow is an alternative to this which is the | |
* normal behavior. | |
* A row is read from the database, an original is built/refreshed/returned | |
* from the shared cache, and the original is registered/conformed/reverted | |
* in the UnitOfWork. | |
* <p> | |
* This default behavior is only safe when the query is executed on a read | |
* connection, otherwise uncommitted data might get loaded into the shared | |
* cache. | |
* <p> | |
* Represents the way TopLink has always worked. | |
*/ | |
protected Object buildWorkingCopyCloneNormally(ObjectBuildingQuery query, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, Object primaryKey, CacheKey preFetchedCacheKey, ClassDescriptor concreteDescriptor, JoinedAttributeManager joinManager) throws DatabaseException, QueryException { | |
// First check local unit of work cache. | |
CacheKey unitOfWorkCacheKey = unitOfWork.getIdentityMapAccessorInstance().acquireLock(primaryKey, concreteDescriptor.getJavaClass(), concreteDescriptor, query.isCacheCheckComplete()); | |
Object clone = unitOfWorkCacheKey.getObject(); | |
boolean found = clone != null; | |
Object original = null; | |
try { | |
// Only check parent cache if not in unit of work, or if a refresh is required. | |
if (!found || query.shouldRefreshIdentityMapResult() | |
|| query.shouldCacheQueryResults() // Need to build original to cache it. | |
|| query.shouldRetrieveBypassCache() | |
|| (concreteDescriptor.hasFetchGroupManager() && concreteDescriptor.getFetchGroupManager().isPartialObject(clone))) { | |
// This is normal case when we are not in transaction. | |
// Pass the query off to the parent. Let it build the object and | |
// cache it normally, then register/refresh it. | |
AbstractSession session = unitOfWork.getParentIdentityMapSession(query); | |
// forwarding queries to different sessions is now as simple as setting | |
// the session on the query. | |
query.setSession(session); | |
if (session.isUnitOfWork()) { | |
// If a nested unit of work, recurse. | |
original = buildObjectInUnitOfWork(query, joinManager, databaseRow, (UnitOfWorkImpl)session, primaryKey, preFetchedCacheKey, concreteDescriptor); | |
//GFBug#404 Pass in joinManager or not based on if shouldCascadeCloneToJoinedRelationship is set to true | |
if (unitOfWork.shouldCascadeCloneToJoinedRelationship()) { | |
return query.registerIndividualResult(original, primaryKey, unitOfWork, joinManager, concreteDescriptor); | |
} else { | |
return query.registerIndividualResult(original, primaryKey, unitOfWork, null, concreteDescriptor); | |
} | |
} else { | |
// PERF: This optimizes the normal case to avoid duplicate cache access. | |
CacheKey parentCacheKey = (CacheKey)buildObject(true, query, databaseRow, session, primaryKey, preFetchedCacheKey, concreteDescriptor, joinManager); | |
original = parentCacheKey.getObject(); | |
if (query.shouldCacheQueryResults()) { | |
query.cacheResult(original); | |
} | |
// PERF: Do not register nor process read-only. | |
if (unitOfWork.isClassReadOnly(original.getClass(), concreteDescriptor)) { | |
// There is an obscure case where they object could be read-only and pessimistic. | |
// Record clone if referenced class has pessimistic locking policy. | |
query.recordCloneForPessimisticLocking(original, unitOfWork); | |
return original; | |
} | |
if (!query.isRegisteringResults()) { | |
return original; | |
} | |
if (clone == null) { | |
clone = unitOfWork.cloneAndRegisterObject(original, parentCacheKey, unitOfWorkCacheKey, concreteDescriptor); | |
// TODO-dclarke: At this point the clones do not have their fetch-group specified | |
// relationship attributes loaded | |
} | |
//bug3659327 | |
//fetch group manager control fetch group support | |
if (concreteDescriptor.hasFetchGroupManager()) { | |
//if the object is already registered in uow, but it's partially fetched (fetch group case) | |
if (concreteDescriptor.getFetchGroupManager().shouldWriteInto(original, clone)) { | |
//there might be cases when reverting/refreshing clone is needed. | |
concreteDescriptor.getFetchGroupManager().writePartialIntoClones(original, clone, unitOfWork.getBackupClone(clone, concreteDescriptor), unitOfWork); | |
} | |
} | |
} | |
} | |
query.postRegisterIndividualResult(clone, original, primaryKey, unitOfWork, joinManager, concreteDescriptor); | |
} finally { | |
unitOfWorkCacheKey.release(); | |
query.setSession(unitOfWork); | |
} | |
return clone; | |
} | |
/** | |
* Return an instance of the receivers javaClass. Set the attributes of an instance | |
* from the values stored in the database row. | |
*/ | |
protected Object buildObject(boolean returnCacheKey, ObjectBuildingQuery query, AbstractRecord databaseRow, AbstractSession session, Object primaryKey, CacheKey preFetchedCacheKey, ClassDescriptor concreteDescriptor, JoinedAttributeManager joinManager) throws DatabaseException, QueryException { | |
boolean isProtected = concreteDescriptor.getCachePolicy().isProtectedIsolation(); | |
if (isProtected && session.isIsolatedClientSession()){ | |
return buildProtectedObject(returnCacheKey, query, databaseRow, session, primaryKey, preFetchedCacheKey, concreteDescriptor, joinManager); | |
} | |
Object domainObject = null; | |
// Cache key is used for object locking. | |
CacheKey cacheKey = null; | |
// Keep track if we actually built/refresh the object. | |
boolean cacheHit = true; | |
boolean isSopQuery = concreteDescriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy(); | |
// has to cache this flag - sopObject is set to null in the row after it has been processed | |
boolean hasSopObject = databaseRow.hasSopObject(); | |
boolean domainWasMissing = true; | |
boolean shouldMaintainCache = query.shouldMaintainCache(); | |
ObjectBuilder concreteObjectBuilder = concreteDescriptor.getObjectBuilder(); | |
try { | |
boolean shouldRetrieveBypassCache = query.shouldRetrieveBypassCache(); | |
boolean shouldStoreBypassCache = query.shouldStoreBypassCache(); | |
// Check if the objects exists in the identity map. | |
if (shouldMaintainCache && (!shouldRetrieveBypassCache || !shouldStoreBypassCache)) { | |
if (preFetchedCacheKey == null){ | |
cacheKey = session.retrieveCacheKey(primaryKey, concreteDescriptor, joinManager, query); | |
}else{ | |
cacheKey = preFetchedCacheKey; | |
cacheKey.acquireLock(query); | |
} | |
if (cacheKey != null){ | |
domainObject = cacheKey.getObject(); | |
} | |
domainWasMissing = domainObject == null; | |
} | |
FetchGroup fetchGroup = query.getExecutionFetchGroup(concreteDescriptor); | |
if (domainWasMissing || shouldRetrieveBypassCache) { | |
cacheHit = false; | |
if (domainObject == null || shouldStoreBypassCache) { | |
if (query.isReadObjectQuery() && ((ReadObjectQuery)query).shouldLoadResultIntoSelectionObject()) { | |
domainObject = ((ReadObjectQuery)query).getSelectionObject(); | |
} else { | |
if (isSopQuery && !hasSopObject) { | |
// serialized sopObject is a value corresponding to sopField in the row, row.sopObject==null; | |
// the following line sets deserialized sopObject into row.sopObject variable and sets sopField's value to null; | |
domainObject = concreteDescriptor.getSerializedObjectPolicy().getObjectFromRow(databaseRow, session, (ObjectLevelReadQuery)query); | |
} | |
if (domainObject == null) { | |
domainObject = concreteObjectBuilder.buildNewInstance(); | |
} | |
} | |
} | |
// The object must be registered before building its attributes to resolve circular dependencies. | |
if (shouldMaintainCache && !shouldStoreBypassCache) { | |
if (domainWasMissing) { // may have build a new domain even though there is one in the cache | |
cacheKey.setObject(domainObject); | |
} | |
copyQueryInfoToCacheKey(cacheKey, query, databaseRow, session, concreteDescriptor); | |
} else if (cacheKey == null || (domainWasMissing && shouldRetrieveBypassCache)) { | |
cacheKey = new CacheKey(primaryKey); | |
cacheKey.setObject(domainObject); | |
} | |
concreteObjectBuilder.buildAttributesIntoObject(domainObject, cacheKey, databaseRow, query, joinManager, fetchGroup, false, session); | |
if (isProtected && (cacheKey != null)) { | |
cacheForeignKeyValues(databaseRow, cacheKey, session); | |
} | |
if (shouldMaintainCache && !shouldStoreBypassCache) { | |
// Set the fetch group to the domain object, after built. | |
if ((fetchGroup != null) && concreteDescriptor.hasFetchGroupManager()) { | |
EntityFetchGroup entityFetchGroup = concreteDescriptor.getFetchGroupManager().getEntityFetchGroup(fetchGroup); | |
if (entityFetchGroup !=null){ | |
entityFetchGroup.setOnEntity(domainObject, session); | |
} | |
} | |
} | |
// PERF: Cache the primary key and cache key if implements PersistenceEntity. | |
if (domainObject instanceof PersistenceEntity) { | |
updateCachedAttributes((PersistenceEntity) domainObject, cacheKey, primaryKey); | |
} | |
} else { | |
if (query.isReadObjectQuery() && ((ReadObjectQuery)query).shouldLoadResultIntoSelectionObject()) { | |
copyInto(domainObject, ((ReadObjectQuery)query).getSelectionObject()); | |
domainObject = ((ReadObjectQuery)query).getSelectionObject(); | |
} | |
//check if the cached object has been invalidated | |
boolean isInvalidated = concreteDescriptor.getCacheInvalidationPolicy().isInvalidated(cacheKey, query.getExecutionTime()); | |
FetchGroupManager concreteFetchGroupManager = null; | |
if (concreteDescriptor.hasFetchGroupManager()) { | |
concreteFetchGroupManager = concreteDescriptor.getFetchGroupManager(); | |
} | |
//CR #4365 - Queryid comparison used to prevent infinite recursion on refresh object cascade all | |
//if the concurrency manager is locked by the merge process then no refresh is required. | |
// bug # 3388383 If this thread does not have the active lock then someone is building the object so in order to maintain data integrity this thread will not | |
// fight to overwrite the object ( this also will avoid potential deadlock situations | |
if ((cacheKey.getActiveThread() == Thread.currentThread()) && ((query.shouldRefreshIdentityMapResult() || concreteDescriptor.shouldAlwaysRefreshCache() || isInvalidated ) && ((cacheKey.getLastUpdatedQueryId() != query.getQueryId()) && !cacheKey.isLockedByMergeManager()))) { | |
cacheHit = refreshObjectIfRequired(concreteDescriptor, cacheKey, cacheKey.getObject(), query, joinManager, databaseRow, session, false); | |
} else if ((concreteFetchGroupManager != null) && (concreteFetchGroupManager.isPartialObject(domainObject) && (!concreteFetchGroupManager.isObjectValidForFetchGroup(domainObject, concreteFetchGroupManager.getEntityFetchGroup(fetchGroup))))) { | |
cacheHit = false; | |
// The fetched object is not sufficient for the fetch group of the query | |
// refresh attributes of the query's fetch group. | |
concreteFetchGroupManager.unionEntityFetchGroupIntoObject(domainObject, concreteFetchGroupManager.getEntityFetchGroup(fetchGroup), session, false); | |
concreteObjectBuilder.buildAttributesIntoObject(domainObject, cacheKey, databaseRow, query, joinManager, fetchGroup, false, session); | |
if (cacheKey != null){ | |
cacheForeignKeyValues(databaseRow, cacheKey, session); | |
} | |
} | |
// 3655915: a query with join/batch'ing that gets a cache hit | |
// may require some attributes' valueholders to be re-built. | |
else if (joinManager != null && joinManager.hasJoinedAttributeExpressions()) { //some queries like ObjRel do not support joining | |
loadJoinedAttributes(concreteDescriptor, domainObject, cacheKey, databaseRow, joinManager, query, false); | |
} else if (query.isReadAllQuery() && ((ReadAllQuery)query).hasBatchReadAttributes()) { | |
loadBatchReadAttributes(concreteDescriptor, domainObject, cacheKey, databaseRow, query, joinManager, false); | |
} | |
} | |
} finally { | |
if (shouldMaintainCache && (cacheKey != null)) { | |
// bug 2681401: | |
// in case of exception (for instance, thrown by buildNewInstance()) | |
// cacheKey.getObject() may be null. | |
if (cacheKey.getObject() != null) { | |
cacheKey.updateAccess(); | |
} | |
// PERF: Only use deferred locking if required. | |
if (query.requiresDeferredLocks()) { | |
cacheKey.releaseDeferredLock(); | |
} else { | |
cacheKey.release(); | |
} | |
} | |
} | |
if (!cacheHit) { | |
concreteObjectBuilder.instantiateEagerMappings(domainObject, session); | |
if (shouldMaintainCache && (cacheKey != null)) { | |
if (hasSopObject || (isSopQuery && this.hasCacheIndexesInSopObject)) { | |
// at least some of the cache index fields are missing from the row - extract index values from domainObject | |
concreteDescriptor.getCachePolicy().indexObjectInCache(cacheKey, domainObject, concreteDescriptor, session, !domainWasMissing); | |
} else { | |
concreteDescriptor.getCachePolicy().indexObjectInCache(cacheKey, databaseRow, domainObject, concreteDescriptor, session, !domainWasMissing); | |
} | |
} | |
} | |
if (query instanceof ObjectLevelReadQuery) { | |
LoadGroup group = ((ObjectLevelReadQuery)query).getLoadGroup(); | |
if (group != null) { | |
session.load(domainObject, group, query.getDescriptor(), false); | |
} | |
} | |
if (returnCacheKey) { | |
return cacheKey; | |
} else { | |
return domainObject; | |
} | |
} | |
/** | |
* Return an instance of the receivers javaClass. Set the attributes of an instance | |
* from the values stored in the database row. | |
*/ | |
protected Object buildProtectedObject(boolean returnCacheKey, ObjectBuildingQuery query, AbstractRecord databaseRow, AbstractSession session, Object primaryKey, CacheKey preFetchedCacheKey, ClassDescriptor concreteDescriptor, JoinedAttributeManager joinManager) throws DatabaseException, QueryException { | |
Object cachedObject = null; | |
Object protectedObject = null; | |
// Cache key is used for object locking. | |
CacheKey cacheKey = null; | |
CacheKey sharedCacheKey = null; | |
// Keep track if we actually built/refresh the object. | |
boolean cacheHit = true; | |
try { | |
// Check if the objects exists in the identity map. | |
if (query.shouldMaintainCache() && (!query.shouldRetrieveBypassCache() || !query.shouldStoreBypassCache())) { | |
cacheKey = session.retrieveCacheKey(primaryKey, concreteDescriptor, joinManager, query); | |
protectedObject = cacheKey.getObject(); | |
} | |
FetchGroup fetchGroup = query.getExecutionFetchGroup(concreteDescriptor); | |
FetchGroupManager fetchGroupManager = concreteDescriptor.getFetchGroupManager(); | |
if (protectedObject == null || query.shouldRetrieveBypassCache()) { | |
cacheHit = false; | |
boolean domainWasMissing = protectedObject == null; | |
if (protectedObject == null || query.shouldStoreBypassCache()){ | |
if (query.isReadObjectQuery() && ((ReadObjectQuery)query).shouldLoadResultIntoSelectionObject()) { | |
protectedObject = ((ReadObjectQuery)query).getSelectionObject(); | |
} else { | |
protectedObject = concreteDescriptor.getObjectBuilder().buildNewInstance(); | |
} | |
} | |
// The object must be registered before building its attributes to resolve circular dependencies. | |
// The object must be registered before building its attributes to resolve circular dependencies. | |
if (query.shouldMaintainCache() && ! query.shouldStoreBypassCache()){ | |
if (domainWasMissing) { // may have build a new domain even though there is one in the cache | |
cacheKey.setObject(protectedObject); | |
} | |
copyQueryInfoToCacheKey(cacheKey, query, databaseRow, session, concreteDescriptor); | |
}else if (cacheKey == null || (domainWasMissing && query.shouldRetrieveBypassCache())){ | |
cacheKey = new CacheKey(primaryKey); | |
cacheKey.setObject(protectedObject); | |
} | |
// The object must be registered before building its attributes to resolve circular dependencies. | |
if (query.shouldMaintainCache() && ! query.shouldStoreBypassCache()) { | |
if (preFetchedCacheKey == null){ | |
sharedCacheKey = session.getParent().retrieveCacheKey(primaryKey, concreteDescriptor, joinManager, query); | |
}else{ | |
sharedCacheKey = preFetchedCacheKey; | |
cacheKey.acquireLock(query); | |
} | |
if (sharedCacheKey.getObject() == null){ | |
sharedCacheKey = (CacheKey) buildObject(true, query, databaseRow, session.getParent(), primaryKey, preFetchedCacheKey, concreteDescriptor, joinManager); | |
cachedObject = sharedCacheKey.getObject(); | |
} | |
} | |
concreteDescriptor.getObjectBuilder().buildAttributesIntoObject(protectedObject, sharedCacheKey, databaseRow, query, joinManager, fetchGroup, false, session); | |
//if !protected the returned object and the domain object are the same. | |
if (query.shouldMaintainCache() && ! query.shouldStoreBypassCache()) { | |
// Set the fetch group to the domain object, after built. | |
if ((fetchGroup != null) && concreteDescriptor.hasFetchGroupManager()) { | |
EntityFetchGroup entityFetchGroup = concreteDescriptor.getFetchGroupManager().getEntityFetchGroup(fetchGroup); | |
if (entityFetchGroup !=null){ | |
entityFetchGroup.setOnEntity(protectedObject, session); | |
} | |
} | |
} | |
// PERF: Cache the primary key and cache key if implements PersistenceEntity. | |
if (protectedObject instanceof PersistenceEntity) { | |
((PersistenceEntity)protectedObject)._persistence_setId(primaryKey); | |
} | |
} else { | |
if (query.isReadObjectQuery() && ((ReadObjectQuery)query).shouldLoadResultIntoSelectionObject()) { | |
copyInto(protectedObject, ((ReadObjectQuery)query).getSelectionObject()); | |
protectedObject = ((ReadObjectQuery)query).getSelectionObject(); | |
} | |
sharedCacheKey = session.getParent().retrieveCacheKey(primaryKey, concreteDescriptor, joinManager, query); | |
cachedObject = sharedCacheKey.getObject(); | |
if (cachedObject == null){ | |
sharedCacheKey = (CacheKey) buildObject(true, query, databaseRow, session.getParent(), primaryKey, preFetchedCacheKey, concreteDescriptor, joinManager); | |
cachedObject = sharedCacheKey.getObject(); | |
} | |
//check if the cached object has been invalidated | |
boolean isInvalidated = concreteDescriptor.getCacheInvalidationPolicy().isInvalidated(sharedCacheKey, query.getExecutionTime()); | |
//CR #4365 - Queryid comparison used to prevent infinite recursion on refresh object cascade all | |
//if the concurrency manager is locked by the merge process then no refresh is required. | |
// bug # 3388383 If this thread does not have the active lock then someone is building the object so in order to maintain data integrity this thread will not | |
// fight to overwrite the object ( this also will avoid potential deadlock situations | |
if ((sharedCacheKey.getActiveThread() == Thread.currentThread()) && ((query.shouldRefreshIdentityMapResult() || concreteDescriptor.shouldAlwaysRefreshCache() || isInvalidated) && ((sharedCacheKey.getLastUpdatedQueryId() != query.getQueryId()) && !sharedCacheKey.isLockedByMergeManager()))) { | |
//need to refresh. shared cache instance | |
cacheHit = refreshObjectIfRequired(concreteDescriptor, sharedCacheKey, cachedObject, query, joinManager, databaseRow, session.getParent(), true); | |
//shared cache was refreshed and a refresh has been requested so lets refresh the protected object as well | |
refreshObjectIfRequired(concreteDescriptor, sharedCacheKey, protectedObject, query, joinManager, databaseRow, session, true); | |
} else if (fetchGroupManager != null && (fetchGroupManager.isPartialObject(protectedObject) && (!fetchGroupManager.isObjectValidForFetchGroup(protectedObject, fetchGroupManager.getEntityFetchGroup(fetchGroup))))) { | |
cacheHit = false; | |
// The fetched object is not sufficient for the fetch group of the query | |
// refresh attributes of the query's fetch group. | |
fetchGroupManager.unionEntityFetchGroupIntoObject(protectedObject, fetchGroupManager.getEntityFetchGroup(fetchGroup), session, false); | |
concreteDescriptor.getObjectBuilder().buildAttributesIntoObject(protectedObject, sharedCacheKey, databaseRow, query, joinManager, fetchGroup, false, session); | |
} | |
// 3655915: a query with join/batch'ing that gets a cache hit | |
// may require some attributes' valueholders to be re-built. | |
else if (joinManager != null && joinManager.hasJoinedAttributeExpressions()) { //some queries like ObjRel do not support joining | |
loadJoinedAttributes(concreteDescriptor, cachedObject, sharedCacheKey, databaseRow, joinManager, query, false); | |
loadJoinedAttributes(concreteDescriptor, protectedObject, sharedCacheKey, databaseRow, joinManager, query, true); | |
} else if (query.isReadAllQuery() && ((ReadAllQuery)query).hasBatchReadAttributes()) { | |
loadBatchReadAttributes(concreteDescriptor, cachedObject, sharedCacheKey, databaseRow, query, joinManager, false); | |
loadBatchReadAttributes(concreteDescriptor, protectedObject, sharedCacheKey, databaseRow, query, joinManager, true); | |
} | |
} | |
} finally { | |
if (query.shouldMaintainCache()){ | |
if (cacheKey != null) { | |
// bug 2681401: | |
// in case of exception (for instance, thrown by buildNewInstance()) | |
// cacheKey.getObject() may be null. | |
if (cacheKey.getObject() != null) { | |
cacheKey.updateAccess(); | |
} | |
// PERF: Only use deferred locking if required. | |
if (query.requiresDeferredLocks()) { | |
cacheKey.releaseDeferredLock(); | |
} else { | |
cacheKey.release(); | |
} | |
} | |
if (sharedCacheKey != null) { | |
// bug 2681401: | |
// in case of exception (for instance, thrown by buildNewInstance()) | |
// sharedCacheKey() may be null. | |
if (sharedCacheKey.getObject() != null) { | |
sharedCacheKey.updateAccess(); | |
} | |
// PERF: Only use deferred locking if required. | |
if (query.requiresDeferredLocks()) { | |
sharedCacheKey.releaseDeferredLock(); | |
} else { | |
sharedCacheKey.release(); | |
} | |
} | |
} | |
} | |
if (!cacheHit) { | |
concreteDescriptor.getObjectBuilder().instantiateEagerMappings(protectedObject, session); | |
} | |
if (returnCacheKey) { | |
return cacheKey; | |
} else { | |
return protectedObject; | |
} | |
} | |
/** | |
* Clean up the cached object data and only revert the fetch group data back to the cached object. | |
*/ | |
private void revertFetchGroupData(Object domainObject, ClassDescriptor concreteDescriptor, CacheKey cacheKey, ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, AbstractSession session, boolean targetIsProtected) { | |
FetchGroup fetchGroup = query.getExecutionFetchGroup(concreteDescriptor); | |
FetchGroupManager fetchGroupManager = concreteDescriptor.getFetchGroupManager(); | |
//the cached object is either invalidated, or staled as the version is newer, or a refresh is explicitly set on the query. | |
//clean all data of the cache object. | |
fetchGroupManager.reset(domainObject); | |
//set fetch group reference to the cached object | |
fetchGroupManager.setObjectFetchGroup(domainObject, fetchGroupManager.getEntityFetchGroup(fetchGroup), session); | |
// Bug 276362 - set the CacheKey's read time (to re-validate the CacheKey) before buildAttributesIntoObject is called | |
cacheKey.setReadTime(query.getExecutionTime()); | |
//read in the fetch group data only | |
concreteDescriptor.getObjectBuilder().buildAttributesIntoObject(domainObject, cacheKey, databaseRow, query, joinManager, fetchGroup, false, session); | |
//set refresh on fetch group | |
fetchGroupManager.setRefreshOnFetchGroupToObject(domainObject, (query.shouldRefreshIdentityMapResult() || concreteDescriptor.shouldAlwaysRefreshCache())); | |
//set query id to prevent infinite recursion on refresh object cascade all | |
cacheKey.setLastUpdatedQueryId(query.getQueryId()); | |
//register the object into the IM and set the write lock object if applied. | |
if (concreteDescriptor.usesOptimisticLocking()) { | |
OptimisticLockingPolicy policy = concreteDescriptor.getOptimisticLockingPolicy(); | |
cacheKey.setWriteLockValue(policy.getValueToPutInCache(databaseRow, session)); | |
} | |
} | |
/** | |
* Return a container which contains the instances of the receivers javaClass. | |
* Set the fields of the instance to the values stored in the database rows. | |
*/ | |
public Object buildObjectsInto(ReadAllQuery query, List databaseRows, Object domainObjects) { | |
if (databaseRows instanceof ThreadCursoredList) { | |
return buildObjectsFromCursorInto(query, databaseRows, domainObjects); | |
} | |
int size = databaseRows.size(); | |
if (size > 0) { | |
AbstractSession session = query.getSession(); | |
session.startOperationProfile(SessionProfiler.ObjectBuilding, query, SessionProfiler.ALL); | |
try { | |
InheritancePolicy inheritancePolicy = null; | |
if (this.descriptor.hasInheritance()) { | |
inheritancePolicy = this.descriptor.getInheritancePolicy(); | |
} | |
boolean isUnitOfWork = session.isUnitOfWork(); | |
boolean shouldCacheQueryResults = query.shouldCacheQueryResults(); | |
boolean shouldUseWrapperPolicy = query.shouldUseWrapperPolicy(); | |
// PERF: Avoid lazy init of join manager if no joining. | |
JoinedAttributeManager joinManager = null; | |
if (query.hasJoining()) { | |
joinManager = query.getJoinedAttributeManager(); | |
} | |
if (this.descriptor.getCachePolicy().shouldPrefetchCacheKeys() && query.shouldMaintainCache() && ! query.shouldRetrieveBypassCache()){ | |
Object[] pkList = new Object[size]; | |
for (int i = 0; i< size; ++i){ | |
pkList[i] = extractPrimaryKeyFromRow((AbstractRecord)databaseRows.get(i), session); | |
} | |
query.setPrefetchedCacheKeys(session.getIdentityMapAccessorInstance().getAllCacheKeysFromIdentityMapWithEntityPK(pkList, descriptor)); | |
} | |
ContainerPolicy policy = query.getContainerPolicy(); | |
if (policy.shouldAddAll()) { | |
List domainObjectsIn = new ArrayList(size); | |
List<AbstractRecord> databaseRowsIn = new ArrayList(size); | |
for (int index = 0; index < size; index++) { | |
AbstractRecord databaseRow = (AbstractRecord)databaseRows.get(index); | |
// PERF: 1-m joining nulls out duplicate rows. | |
if (databaseRow != null) { | |
domainObjectsIn.add(buildObject(query, databaseRow, joinManager, session, this.descriptor, inheritancePolicy, | |
isUnitOfWork, shouldCacheQueryResults, shouldUseWrapperPolicy)); | |
databaseRowsIn.add(databaseRow); | |
} | |
} | |
policy.addAll(domainObjectsIn, domainObjects, session, databaseRowsIn, query, (CacheKey)null, true); | |
} else { | |
boolean quickAdd = (domainObjects instanceof Collection) && !this.hasWrapperPolicy; | |
for (int index = 0; index < size; index++) { | |
AbstractRecord databaseRow = (AbstractRecord)databaseRows.get(index); | |
// PERF: 1-m joining nulls out duplicate rows. | |
if (databaseRow != null) { | |
Object domainObject = buildObject(query, databaseRow, joinManager, session, this.descriptor, inheritancePolicy, | |
isUnitOfWork, shouldCacheQueryResults, shouldUseWrapperPolicy); | |
if (quickAdd) { | |
((Collection)domainObjects).add(domainObject); | |
} else { | |
policy.addInto(domainObject, domainObjects, session, databaseRow, query, (CacheKey)null, true); | |
} | |
} | |
} | |
} | |
} finally { | |
session.endOperationProfile(SessionProfiler.ObjectBuilding, query, SessionProfiler.ALL); | |
} | |
} | |
return domainObjects; | |
} | |
/** | |
* Version of buildObjectsInto method that takes call instead of rows. | |
* Return a container which contains the instances of the receivers javaClass. | |
* Set the fields of the instance to the values stored in the result set. | |
*/ | |
public Object buildObjectsFromResultSetInto(ReadAllQuery query, ResultSet resultSet, Vector fields, DatabaseField[] fieldsArray, Object domainObjects) throws SQLException { | |
AbstractSession session = query.getSession(); | |
session.startOperationProfile(SessionProfiler.ObjectBuilding, query, SessionProfiler.ALL); | |
try { | |
boolean hasNext = resultSet.next(); | |
if (hasNext) { | |
InheritancePolicy inheritancePolicy = null; | |
if (this.descriptor.hasInheritance()) { | |
inheritancePolicy = this.descriptor.getInheritancePolicy(); | |
} | |
boolean isUnitOfWork = session.isUnitOfWork(); | |
boolean shouldCacheQueryResults = query.shouldCacheQueryResults(); | |
boolean shouldUseWrapperPolicy = query.shouldUseWrapperPolicy(); | |
// PERF: Avoid lazy init of join manager if no joining. | |
JoinedAttributeManager joinManager = null; | |
if (query.hasJoining()) { | |
joinManager = query.getJoinedAttributeManager(); | |
} | |
ContainerPolicy policy = query.getContainerPolicy(); | |
// !cp.shouldAddAll() - query with SortedListContainerPolicy - currently does not use this method | |
boolean quickAdd = (domainObjects instanceof Collection) && !this.hasWrapperPolicy; | |
ResultSetMetaData metaData = resultSet.getMetaData(); | |
ResultSetRecord row = null; | |
AbstractSession executionSession = query.getExecutionSession(); | |
DatabaseAccessor dbAccessor = (DatabaseAccessor)query.getAccessor(); | |
DatabasePlatform platform = dbAccessor.getPlatform(); | |
boolean optimizeData = platform.shouldOptimizeDataConversion(); | |
if (this.isSimple) { | |
// None of the fields are relational - the row could be reused, just clear all the values. | |
row = new SimpleResultSetRecord(fields, fieldsArray, resultSet, metaData, dbAccessor, executionSession, platform, optimizeData); | |
if (this.descriptor.isDescriptorTypeAggregate()) { | |
// Aggregate Collection may have an unmapped primary key referencing the owner, the corresponding field will not be used when the object is populated and therefore may not be cleared. | |
((SimpleResultSetRecord)row).setShouldKeepValues(true); | |
} | |
} | |
while (hasNext) { | |
if (!this.isSimple) { | |
row = new ResultSetRecord(fields, fieldsArray, resultSet, metaData, dbAccessor, executionSession, platform, optimizeData); | |
} | |
Object domainObject = buildObject(query, row, joinManager, session, this.descriptor, inheritancePolicy, | |
isUnitOfWork, shouldCacheQueryResults, shouldUseWrapperPolicy); | |
if (quickAdd) { | |
((Collection)domainObjects).add(domainObject); | |
} else { | |
// query with MappedKeyMapPolicy currently does not use this method | |
policy.addInto(domainObject, domainObjects, session); | |
} | |
if (this.isSimple) { | |
((SimpleResultSetRecord)row).reset(); | |
} else { | |
if (this.shouldKeepRow) { | |
if (row.hasResultSet()) { | |
// ResultSet has not been fully triggered - that means the cached object was used. | |
// Yet the row still may be cached in a value holder (see loadBatchReadAttributes and loadJoinedAttributes methods). | |
// Remove ResultSet to avoid attempt to trigger it (already closed) when pk or fk values (already extracted) accessed when the value holder is instantiated. | |
row.removeResultSet(); | |
} else { | |
row.removeNonIndirectionValues(); | |
} | |
} | |
} | |
hasNext = resultSet.next(); | |
} | |
} | |
} finally { | |
session.endOperationProfile(SessionProfiler.ObjectBuilding, query, SessionProfiler.ALL); | |
} | |
return domainObjects; | |
} | |
/** | |
* Return a container which contains the instances of the receivers javaClass. | |
* Set the fields of the instance to the values stored in the database rows. | |
*/ | |
public Object buildObjectsFromCursorInto(ReadAllQuery query, List databaseRows, Object domainObjects) { | |
AbstractSession session = query.getSession(); | |
session.startOperationProfile(SessionProfiler.ObjectBuilding, query, SessionProfiler.ALL); | |
try { | |
InheritancePolicy inheritancePolicy = null; | |
if (this.descriptor.hasInheritance()) { | |
inheritancePolicy = this.descriptor.getInheritancePolicy(); | |
} | |
boolean isUnitOfWork = session.isUnitOfWork(); | |
boolean shouldCacheQueryResults = query.shouldCacheQueryResults(); | |
boolean shouldUseWrapperPolicy = query.shouldUseWrapperPolicy(); | |
// PERF: Avoid lazy init of join manager if no joining. | |
JoinedAttributeManager joinManager = null; | |
if (query.hasJoining()) { | |
joinManager = query.getJoinedAttributeManager(); | |
} | |
ContainerPolicy policy = query.getContainerPolicy(); | |
if (policy.shouldAddAll()) { | |
List domainObjectsIn = new ArrayList(); | |
List<AbstractRecord> databaseRowsIn = new ArrayList(); | |
for (Enumeration iterator = ((Vector)databaseRows).elements(); iterator.hasMoreElements(); ) { | |
AbstractRecord databaseRow = (AbstractRecord)iterator.nextElement(); | |
// PERF: 1-m joining nulls out duplicate rows. | |
if (databaseRow != null) { | |
domainObjectsIn.add(buildObject(query, databaseRow, joinManager, session, this.descriptor, inheritancePolicy, | |
isUnitOfWork, shouldCacheQueryResults, shouldUseWrapperPolicy)); | |
databaseRowsIn.add(databaseRow); | |
} | |
} | |
policy.addAll(domainObjectsIn, domainObjects, session, databaseRowsIn, query, (CacheKey)null, true); | |
} else { | |
boolean quickAdd = (domainObjects instanceof Collection) && !this.hasWrapperPolicy; | |
for (Enumeration iterator = ((Vector)databaseRows).elements(); iterator.hasMoreElements(); ) { | |
AbstractRecord databaseRow = (AbstractRecord)iterator.nextElement(); | |
// PERF: 1-m joining nulls out duplicate rows. | |
if (databaseRow != null) { | |
Object domainObject = buildObject(query, databaseRow, joinManager, session, this.descriptor, inheritancePolicy, | |
isUnitOfWork, shouldCacheQueryResults, shouldUseWrapperPolicy); | |
if (quickAdd) { | |
((Collection)domainObjects).add(domainObject); | |
} else { | |
policy.addInto(domainObject, domainObjects, session, databaseRow, query, (CacheKey)null, true); | |
} | |
} | |
} | |
} | |
} finally { | |
session.endOperationProfile(SessionProfiler.ObjectBuilding, query, SessionProfiler.ALL); | |
} | |
return domainObjects; | |
} | |
/** | |
* Build the primary key expression for the secondary table. | |
*/ | |
public Expression buildPrimaryKeyExpression(DatabaseTable table) throws DescriptorException { | |
if (this.descriptor.getTables().firstElement().equals(table)) { | |
return getPrimaryKeyExpression(); | |
} | |
Map keyMapping = this.descriptor.getAdditionalTablePrimaryKeyFields().get(table); | |
if (keyMapping == null) { | |
throw DescriptorException.multipleTablePrimaryKeyNotSpecified(this.descriptor); | |
} | |
ExpressionBuilder builder = new ExpressionBuilder(); | |
Expression expression = null; | |
for (Iterator primaryKeyEnum = keyMapping.values().iterator(); primaryKeyEnum.hasNext();) { | |
DatabaseField field = (DatabaseField)primaryKeyEnum.next(); | |
expression = (builder.getField(field).equal(builder.getParameter(field))).and(expression); | |
} | |
return expression; | |
} | |
/** | |
* Build the primary key expression from the specified primary key values. | |
*/ | |
public Expression buildPrimaryKeyExpressionFromKeys(Object primaryKey, AbstractSession session) { | |
Expression builder = new ExpressionBuilder(); | |
List<DatabaseField> primaryKeyFields = this.descriptor.getPrimaryKeyFields(); | |
if (this.descriptor.getCachePolicy().getCacheKeyType() == CacheKeyType.ID_VALUE) { | |
return builder.getField(primaryKeyFields.get(0)).equal(primaryKey); | |
} | |
Expression expression = null; | |
int size = primaryKeyFields.size(); | |
Object[] primaryKeyValues = null; | |
if (primaryKey == null) { | |
primaryKeyValues = new Object[size]; | |
} else { | |
primaryKeyValues = ((CacheId)primaryKey).getPrimaryKey(); | |
} | |
for (int index = 0; index < size; index++) { | |
Object value = primaryKeyValues[index]; | |
DatabaseField field = primaryKeyFields.get(index); | |
if (value != null) { | |
Expression subExpression = builder.getField(field).equal(value); | |
expression = subExpression.and(expression); | |
} | |
} | |
return expression; | |
} | |
/** | |
* Build the primary key expression from the specified domain object. | |
*/ | |
public Expression buildPrimaryKeyExpressionFromObject(Object domainObject, AbstractSession session) { | |
return buildPrimaryKeyExpressionFromKeys(extractPrimaryKeyFromObject(domainObject, session), session); | |
} | |
/** | |
* Build the row representation of an object. | |
*/ | |
public AbstractRecord buildRow(Object object, AbstractSession session, WriteType writeType) { | |
return buildRow(createRecord(session), object, session, writeType); | |
} | |
/** | |
* Build the row representation of an object. | |
*/ | |
public AbstractRecord buildRow(AbstractRecord databaseRow, Object object, AbstractSession session, WriteType writeType) { | |
// PERF: Avoid synchronized enumerator as is concurrency bottleneck. | |
List mappings = this.descriptor.getMappings(); | |
int mappingsSize = mappings.size(); | |
for (int index = 0; index < mappingsSize; index++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); | |
mapping.writeFromObjectIntoRow(object, databaseRow, session, writeType); | |
} | |
// If this descriptor is involved in inheritance add the class type. | |
if (this.descriptor.hasInheritance()) { | |
this.descriptor.getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow); | |
} | |
// If this descriptor has multiple tables then we need to append the primary keys for | |
// the non default tables. | |
if (this.descriptor.hasMultipleTables() && !this.descriptor.isAggregateDescriptor()) { | |
addPrimaryKeyForNonDefaultTable(databaseRow, object, session); | |
} | |
// If the session uses multi-tenancy, add the tenant id field. | |
if (getDescriptor().hasMultitenantPolicy()) { | |
getDescriptor().getMultitenantPolicy().addFieldsToRow(databaseRow, session); | |
} | |
return databaseRow; | |
} | |
/** | |
* Build the row representation of the object for update. The row built does not | |
* contain entries for uninstantiated attributes. | |
*/ | |
public AbstractRecord buildRowForShallowInsert(Object object, AbstractSession session) { | |
return buildRowForShallowInsert(createRecord(session), object, session); | |
} | |
/** | |
* Build the row representation of the object for update. The row built does not | |
* contain entries for uninstantiated attributes. | |
*/ | |
public AbstractRecord buildRowForShallowInsert(AbstractRecord databaseRow, Object object, AbstractSession session) { | |
// PERF: Avoid synchronized enumerator as is concurrency bottleneck. | |
List mappings = this.descriptor.getMappings(); | |
int mappingsSize = mappings.size(); | |
for (int index = 0; index < mappingsSize; index++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); | |
mapping.writeFromObjectIntoRowForShallowInsert(object, databaseRow, session); | |
} | |
// If this descriptor is involved in inheritance add the class type. | |
if (this.descriptor.hasInheritance()) { | |
this.descriptor.getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow); | |
} | |
// If this descriptor has multiple tables then we need to append the primary keys for | |
// the non default tables. | |
if (!this.descriptor.isAggregateDescriptor()) { | |
addPrimaryKeyForNonDefaultTable(databaseRow, object, session); | |
} | |
// If the session uses multi-tenancy, add the tenant id field. | |
if (getDescriptor().hasMultitenantPolicy()) { | |
getDescriptor().getMultitenantPolicy().addFieldsToRow(databaseRow, session); | |
} | |
return databaseRow; | |
} | |
/** | |
* Build the row representation of the object that contains only the fields nullified by shallow insert. | |
*/ | |
public AbstractRecord buildRowForUpdateAfterShallowInsert(Object object, AbstractSession session, DatabaseTable table) { | |
return buildRowForUpdateAfterShallowInsert(createRecord(session), object, session, table); | |
} | |
/** | |
* Build the row representation of the object that contains only the fields nullified by shallow insert. | |
*/ | |
public AbstractRecord buildRowForUpdateAfterShallowInsert(AbstractRecord databaseRow, Object object, AbstractSession session, DatabaseTable table) { | |
for (DatabaseMapping mapping : this.descriptor.getMappings()) { | |
mapping.writeFromObjectIntoRowForUpdateAfterShallowInsert(object, databaseRow, session, table); | |
} | |
return databaseRow; | |
} | |
/** | |
* Build the row representation of the object that contains only the fields nullified by shallow insert, with all values set to null. | |
*/ | |
public AbstractRecord buildRowForUpdateBeforeShallowDelete(Object object, AbstractSession session, DatabaseTable table) { | |
return buildRowForUpdateBeforeShallowDelete(createRecord(session), object, session, table); | |
} | |
/** | |
* Build the row representation of the object that contains only the fields nullified by shallow insert, with all values set to null. | |
*/ | |
public AbstractRecord buildRowForUpdateBeforeShallowDelete(AbstractRecord databaseRow, Object object, AbstractSession session, DatabaseTable table) { | |
for (DatabaseMapping mapping : this.descriptor.getMappings()) { | |
mapping.writeFromObjectIntoRowForUpdateBeforeShallowDelete(object, databaseRow, session, table); | |
} | |
return databaseRow; | |
} | |
/** | |
* Build the row representation of an object. | |
* This is only used for aggregates. | |
*/ | |
public AbstractRecord buildRowWithChangeSet(AbstractRecord databaseRow, ObjectChangeSet objectChangeSet, AbstractSession session, WriteType writeType) { | |
List<ChangeRecord> changes = (List)objectChangeSet.getChanges(); | |
int size = changes.size(); | |
for (int index = 0; index < size; index++) { | |
ChangeRecord changeRecord = changes.get(index); | |
DatabaseMapping mapping = changeRecord.getMapping(); | |
mapping.writeFromObjectIntoRowWithChangeRecord(changeRecord, databaseRow, session, writeType); | |
} | |
// If this descriptor is involved in inheritance add the class type. | |
if (this.descriptor.hasInheritance()) { | |
this.descriptor.getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow); | |
} | |
// If the session uses multi-tenancy, add the tenant id field. | |
if (getDescriptor().hasMultitenantPolicy()) { | |
getDescriptor().getMultitenantPolicy().addFieldsToRow(databaseRow, session); | |
} | |
return databaseRow; | |
} | |
/** | |
* Build the row representation of an object. The row built is used only for translations | |
* for the expressions in the expression framework. | |
*/ | |
public AbstractRecord buildRowForTranslation(Object object, AbstractSession session) { | |
AbstractRecord databaseRow = createRecord(session); | |
List<DatabaseMapping> primaryKeyMappings = getPrimaryKeyMappings(); | |
int size = primaryKeyMappings.size(); | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = primaryKeyMappings.get(index); | |
if (mapping != null) { | |
mapping.writeFromObjectIntoRow(object, databaseRow, session, WriteType.UNDEFINED); | |
} | |
} | |
// If this descriptor has multiple tables then we need to append the primary keys for | |
// the non default tables, this is require for m-m, dc defined in the Builder that prefixes the wrong table name. | |
// Ideally the mappings should take part in building the translation row so they can add required values. | |
if (this.descriptor.hasMultipleTables()) { | |
addPrimaryKeyForNonDefaultTable(databaseRow, object, session); | |
} | |
return databaseRow; | |
} | |
/** | |
* Build the row representation of the object for update. The row built does not | |
* contain entries for unchanged attributes. | |
*/ | |
public AbstractRecord buildRowForUpdate(WriteObjectQuery query) { | |
AbstractRecord databaseRow = createRecord(query.getSession()); | |
return buildRowForUpdate(databaseRow, query); | |
} | |
/** | |
* Build into the row representation of the object for update. The row does not | |
* contain entries for unchanged attributes. | |
*/ | |
public AbstractRecord buildRowForUpdate(AbstractRecord databaseRow, WriteObjectQuery query) { | |
for (Iterator mappings = getNonPrimaryKeyMappings().iterator(); mappings.hasNext();) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.next(); | |
mapping.writeFromObjectIntoRowForUpdate(query, databaseRow); | |
} | |
// If this descriptor is involved in inheritance and is an Aggregate, add the class type. | |
// Added Nov 8, 2000 Mostly by PWK but also JED | |
// Prs 24801 | |
// Modified Dec 11, 2000 TGW with assistance from PWK | |
// Prs 27554 | |
if (this.descriptor.hasInheritance() && this.descriptor.isAggregateDescriptor()) { | |
if (query.getObject() != null) { | |
if (query.getBackupClone() == null) { | |
this.descriptor.getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow); | |
} else { | |
if (!query.getObject().getClass().equals(query.getBackupClone().getClass())) { | |
this.descriptor.getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow); | |
} | |
} | |
} | |
} | |
// If the session uses multi-tenancy, add the tenant id field. | |
if (getDescriptor().hasMultitenantPolicy()) { | |
getDescriptor().getMultitenantPolicy().addFieldsToRow(databaseRow, query.getExecutionSession()); | |
} | |
return databaseRow; | |
} | |
/** | |
* Build the row representation of the object for update. The row built does not | |
* contain entries for uninstantiated attributes. | |
*/ | |
public AbstractRecord buildRowForUpdateWithChangeSet(WriteObjectQuery query) { | |
AbstractRecord databaseRow = createRecord(query.getSession()); | |
AbstractSession session = query.getSession(); | |
List changes = query.getObjectChangeSet().getChanges(); | |
int size = changes.size(); | |
for (int index = 0; index < size; index++) { | |
ChangeRecord changeRecord = (ChangeRecord)changes.get(index); | |
DatabaseMapping mapping = changeRecord.getMapping(); | |
mapping.writeFromObjectIntoRowWithChangeRecord(changeRecord, databaseRow, session, WriteType.UPDATE); | |
} | |
return databaseRow; | |
} | |
/** | |
* Build the row representation of an object. | |
*/ | |
public AbstractRecord buildRowForWhereClause(ObjectLevelModifyQuery query) { | |
AbstractRecord databaseRow = createRecord(query.getSession()); | |
// EL bug 319759 | |
if (query.isUpdateObjectQuery()) { | |
query.setShouldValidateUpdateCallCacheUse(true); | |
} | |
for (Iterator mappings = this.descriptor.getMappings().iterator(); | |
mappings.hasNext();) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.next(); | |
mapping.writeFromObjectIntoRowForWhereClause(query, databaseRow); | |
} | |
// If this descriptor has multiple tables then we need to append the primary keys for | |
// the non default tables. | |
if (!this.descriptor.isAggregateDescriptor()) { | |
addPrimaryKeyForNonDefaultTable(databaseRow); | |
} | |
return databaseRow; | |
} | |
/** | |
* Build the row from the primary key values. | |
*/ | |
public AbstractRecord writeIntoRowFromPrimaryKeyValues(AbstractRecord row, Object primaryKey, AbstractSession session, boolean convert) { | |
List<DatabaseField> primaryKeyFields = this.descriptor.getPrimaryKeyFields(); | |
if (this.descriptor.getCachePolicy().getCacheKeyType() == CacheKeyType.ID_VALUE) { | |
DatabaseField field = primaryKeyFields.get(0); | |
Object value = primaryKey; | |
value = session.getPlatform(this.descriptor.getJavaClass()).getConversionManager().convertObject(value, field.getType()); | |
row.put(field, value); | |
return row; | |
} | |
int size = primaryKeyFields.size(); | |
Object[] primaryKeyValues = ((CacheId)primaryKey).getPrimaryKey(); | |
for (int index = 0; index < size; index++) { | |
DatabaseField field = primaryKeyFields.get(index); | |
Object value = primaryKeyValues[index]; | |
value = session.getPlatform(this.descriptor.getJavaClass()).getConversionManager().convertObject(value, field.getType()); | |
row.put(field, value); | |
} | |
return row; | |
} | |
/** | |
* Build the row from the primary key values. | |
*/ | |
public AbstractRecord buildRowFromPrimaryKeyValues(Object key, AbstractSession session) { | |
AbstractRecord databaseRow = createRecord(this.descriptor.getPrimaryKeyFields().size(), session); | |
return writeIntoRowFromPrimaryKeyValues(databaseRow, key, session, true); | |
} | |
/** | |
* Build the row of all of the fields used for insertion. | |
*/ | |
public AbstractRecord buildTemplateInsertRow(AbstractSession session) { | |
AbstractRecord databaseRow = createRecord(session); | |
buildTemplateInsertRow(session, databaseRow); | |
return databaseRow; | |
} | |
public void buildTemplateInsertRow(AbstractSession session, AbstractRecord databaseRow) { | |
for (Iterator mappings = this.descriptor.getMappings().iterator(); | |
mappings.hasNext();) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.next(); | |
mapping.writeInsertFieldsIntoRow(databaseRow, session); | |
} | |
// If this descriptor is involved in inheritance add the class type. | |
if (this.descriptor.hasInheritance()) { | |
this.descriptor.getInheritancePolicy().addClassIndicatorFieldToInsertRow(databaseRow); | |
} | |
// If this descriptor has multiple tables then we need to append the primary keys for | |
// the non default tables. | |
if (!this.descriptor.isAggregateDescriptor()) { | |
addPrimaryKeyForNonDefaultTable(databaseRow); | |
} | |
if (this.descriptor.usesOptimisticLocking()) { | |
this.descriptor.getOptimisticLockingPolicy().addLockFieldsToUpdateRow(databaseRow, session); | |
} | |
// If the session uses multi-tenancy, add the tenant id field. | |
if (this.descriptor.hasMultitenantPolicy()) { | |
this.descriptor.getMultitenantPolicy().addFieldsToRow(databaseRow, session); | |
} | |
if (this.descriptor.hasSerializedObjectPolicy()) { | |
databaseRow.put(this.descriptor.getSerializedObjectPolicy().getField(), null); | |
} | |
// remove any fields from the databaseRow | |
trimFieldsForInsert(session, databaseRow); | |
} | |
/** | |
* INTERNAL | |
* Remove a potential sequence number field and invoke the ReturningPolicy trimModifyRowsForInsert method | |
*/ | |
public void trimFieldsForInsert(AbstractSession session, AbstractRecord databaseRow) { | |
ClassDescriptor descriptor = this.descriptor; | |
if (descriptor.usesSequenceNumbers() && descriptor.getSequence().shouldAcquireValueAfterInsert()) { | |
databaseRow.remove(descriptor.getSequenceNumberField()); | |
} | |
if (descriptor.hasReturningPolicy()) { | |
descriptor.getReturningPolicy().trimModifyRowForInsert(databaseRow); | |
} | |
} | |
/** | |
* Build the row representation of the object for update. The row built does not | |
* contain entries for uninstantiated attributes. | |
*/ | |
public AbstractRecord buildTemplateUpdateRow(AbstractSession session) { | |
AbstractRecord databaseRow = createRecord(session); | |
for (Iterator mappings = getNonPrimaryKeyMappings().iterator(); | |
mappings.hasNext();) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.next(); | |
mapping.writeUpdateFieldsIntoRow(databaseRow, session); | |
} | |
if (this.descriptor.usesOptimisticLocking()) { | |
this.descriptor.getOptimisticLockingPolicy().addLockFieldsToUpdateRow(databaseRow, session); | |
} | |
if (this.descriptor.hasSerializedObjectPolicy()) { | |
databaseRow.put(this.descriptor.getSerializedObjectPolicy().getField(), null); | |
} | |
return databaseRow; | |
} | |
/** | |
* Build and return the expression to use as the where clause to an update object. | |
* The row is passed to allow the version number to be extracted from it. | |
*/ | |
public Expression buildUpdateExpression(DatabaseTable table, AbstractRecord transactionRow, AbstractRecord modifyRow) { | |
// Only the first table must use the lock check. | |
Expression primaryKeyExpression = buildPrimaryKeyExpression(table); | |
if (this.descriptor.usesOptimisticLocking()) { | |
return this.descriptor.getOptimisticLockingPolicy().buildUpdateExpression(table, primaryKeyExpression, transactionRow, modifyRow); | |
} else { | |
return primaryKeyExpression; | |
} | |
} | |
/** | |
* INTERNAL: | |
* Build just the primary key mappings into the object. | |
*/ | |
public void buildPrimaryKeyAttributesIntoObject(Object original, AbstractRecord databaseRow, ObjectBuildingQuery query, AbstractSession session) throws DatabaseException, QueryException { | |
// PERF: Avoid synchronized enumerator as is concurrency bottleneck. | |
List mappings = this.primaryKeyMappings; | |
int mappingsSize = mappings.size(); | |
for (int i = 0; i < mappingsSize; i++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(i); | |
mapping.buildShallowOriginalFromRow(databaseRow, original, null, query, session); | |
} | |
} | |
/** | |
* INTERNAL: | |
* For reading through the write connection when in transaction, | |
* We need a partially populated original, so that we | |
* can build a clone using the copy policy, even though we can't | |
* put this original in the shared cache yet; just build a | |
* shallow original (i.e. just enough to copy over the primary | |
* key and some direct attributes) and keep it on the UOW. | |
*/ | |
public void buildAttributesIntoShallowObject(Object original, AbstractRecord databaseRow, ObjectBuildingQuery query) throws DatabaseException, QueryException { | |
AbstractSession executionSession = query.getSession().getExecutionSession(query); | |
// PERF: Avoid synchronized enumerator as is concurrency bottleneck. | |
List pkMappings = getPrimaryKeyMappings(); | |
int mappingsSize = pkMappings.size(); | |
for (int i = 0; i < mappingsSize; i++) { | |
DatabaseMapping mapping = (DatabaseMapping)pkMappings.get(i); | |
//if (query.shouldReadMapping(mapping)) { | |
if (!mapping.isAbstractColumnMapping()) { | |
mapping.buildShallowOriginalFromRow(databaseRow, original, null, query, executionSession); | |
} | |
} | |
List mappings = this.descriptor.getMappings(); | |
mappingsSize = mappings.size(); | |
for (int i = 0; i < mappingsSize; i++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(i); | |
//if (query.shouldReadMapping(mapping)) { | |
if (mapping.isAbstractColumnMapping()) { | |
mapping.buildShallowOriginalFromRow(databaseRow, original, null, query, executionSession); | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* For reading through the write connection when in transaction, | |
* populate the clone directly from the database row. | |
*/ | |
public void buildAttributesIntoWorkingCopyClone(Object clone, CacheKey sharedCacheKey, ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, boolean forRefresh) throws DatabaseException, QueryException { | |
if (this.descriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy()) { | |
if (buildAttributesIntoWorkingCopyCloneSOP(clone, sharedCacheKey, query, joinManager, databaseRow, unitOfWork, forRefresh)) { | |
return; | |
} | |
} | |
// PERF: Cache if all mappings should be read. | |
boolean readAllMappings = query.shouldReadAllMappings(); | |
List mappings = this.descriptor.getMappings(); | |
int size = mappings.size(); | |
FetchGroup executionFetchGroup = query.getExecutionFetchGroup(this.descriptor); | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); | |
if (readAllMappings || query.shouldReadMapping(mapping, executionFetchGroup)) { | |
mapping.buildCloneFromRow(databaseRow, joinManager, clone, sharedCacheKey, query, unitOfWork, unitOfWork); | |
} | |
} | |
// PERF: Avoid events if no listeners. | |
if (this.descriptor.getEventManager().hasAnyEventListeners()) { | |
postBuildAttributesIntoWorkingCopyCloneEvent(clone, databaseRow, query, unitOfWork, forRefresh); | |
} | |
} | |
/** | |
* For reading through the write connection when in transaction, | |
* populate the clone directly from the database row. | |
* Should not be called unless (this.descriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy()) | |
* This method populates the object only in if some mappings potentially should be read using sopObject and other mappings - not using it. | |
* That happens when the row has been just read from the database and potentially has serialized object still in deserialized bits as a field value. | |
* Note that clone == sopObject is the same case, but (because clone has to be set into cache beforehand) extraction of sopObject | |
* from bit was done right before this method is called. | |
* If attempt to deserialize sopObject from bits has failed, but SOP was setup to allow recovery | |
* (all mapped all fields/value mapped to the object were read, not just those excluded from SOP) | |
* then fall through to buildAttributesIntoWorkingCopyClone. | |
* Nothing should be done if sopObject is not null, but clone != sopObject: | |
* the only way to get into this case should be with original query not maintaining cache, | |
* through a back reference to the original object, which is already being built (or has been built). | |
* @return whether the object has been populated with attributes, if not then buildAttributesIntoWorkingCopyClone should be called. | |
*/ | |
protected boolean buildAttributesIntoWorkingCopyCloneSOP(Object clone, CacheKey sharedCacheKey, ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, boolean forRefresh) throws DatabaseException { | |
Object sopObject = databaseRow.getSopObject(); | |
if (clone == sopObject) { | |
// clone is sopObject | |
// PERF: Cache if all mappings should be read. | |
boolean readAllMappings = query.shouldReadAllMappings(); | |
FetchGroup executionFetchGroup = query.getExecutionFetchGroup(this.descriptor); | |
for (DatabaseMapping mapping : this.descriptor.getMappings()) { | |
if (readAllMappings || query.shouldReadMapping(mapping, executionFetchGroup)) { | |
// to avoid re-setting the same attribute value to domainObject | |
// only populate if either mapping (possibly nested) may reference entity or mapping does not use sopObject | |
if (mapping.hasNestedIdentityReference() || mapping.isOutOnlySopObject()) { | |
if (mapping.isOutSopObject()) { | |
// the mapping should be processed as if there is no sopObject | |
databaseRow.setSopObject(null); | |
mapping.buildCloneFromRow(databaseRow, joinManager, clone, sharedCacheKey, query, unitOfWork, unitOfWork); | |
} else { | |
databaseRow.setSopObject(sopObject); | |
mapping.buildCloneFromRow(databaseRow, joinManager, clone, sharedCacheKey, query, unitOfWork, unitOfWork); | |
} | |
} | |
} | |
} | |
// PERF: Avoid events if no listeners. | |
if (this.descriptor.hasEventManager()) { | |
postBuildAttributesIntoWorkingCopyCloneEvent(clone, databaseRow, query, unitOfWork, forRefresh); | |
} | |
// sopObject has been processed by all relevant mappings, no longer required. | |
databaseRow.setSopObject(null); | |
return true; | |
} else { | |
if (sopObject == null) { | |
// serialized sopObject is a value corresponding to sopField in the row, row.sopObject==null; | |
// the following line sets deserialized sopObject into row.sopObject variable and sets sopField's value to null; | |
sopObject = this.descriptor.getSerializedObjectPolicy().getObjectFromRow(databaseRow, unitOfWork, (ObjectLevelReadQuery)query); | |
if (sopObject != null) { | |
// PERF: Cache if all mappings should be read. | |
boolean readAllMappings = query.shouldReadAllMappings(); | |
FetchGroup executionFetchGroup = query.getExecutionFetchGroup(this.descriptor); | |
for (DatabaseMapping mapping : this.descriptor.getMappings()) { | |
if (readAllMappings || query.shouldReadMapping(mapping, executionFetchGroup)) { | |
if (mapping.isOutSopObject()) { | |
// the mapping should be processed as if there is no sopObject | |
databaseRow.setSopObject(null); | |
mapping.buildCloneFromRow(databaseRow, joinManager, clone, sharedCacheKey, query, unitOfWork, unitOfWork); | |
} else { | |
databaseRow.setSopObject(sopObject); | |
mapping.buildCloneFromRow(databaseRow, joinManager, clone, sharedCacheKey, query, unitOfWork, unitOfWork); | |
} | |
} | |
} | |
// PERF: Avoid events if no listeners. | |
if (this.descriptor.hasEventManager()) { | |
postBuildAttributesIntoWorkingCopyCloneEvent(clone, databaseRow, query, unitOfWork, forRefresh); | |
} | |
// sopObject has been processed by all relevant mappings, no longer required. | |
databaseRow.setSopObject(null); | |
return true; | |
} else { | |
// SOP failed to create sopObject, but exception hasn't been thrown. | |
// That means recovery is possible - fall through to to buildAttributesIntoWorkingCopyClone | |
return false; | |
} | |
} else { | |
// A mapping under SOP can't have another SOP on its reference descriptor, | |
// but that's what seem to be happening. | |
// The only way to get here should be with original query not maintaining cache, | |
// through a back reference to the original object, which is already being built (or has been built). | |
// Leave without building. | |
return true; | |
} | |
} | |
} | |
protected void postBuildAttributesIntoWorkingCopyCloneEvent(Object clone, AbstractRecord databaseRow, ObjectBuildingQuery query, UnitOfWorkImpl unitOfWork, boolean forRefresh) { | |
// Need to run post build or refresh selector, currently check with the query for this, | |
// I'm not sure which should be called it case of refresh building a new object, currently refresh is used... | |
DescriptorEvent event = new DescriptorEvent(clone); | |
event.setQuery(query); | |
event.setSession(unitOfWork); | |
event.setDescriptor(this.descriptor); | |
event.setRecord(databaseRow); | |
if (forRefresh) { | |
event.setEventCode(DescriptorEventManager.PostRefreshEvent); | |
} else { | |
event.setEventCode(DescriptorEventManager.PostBuildEvent); | |
//fire a postBuildEvent then the postCloneEvent | |
unitOfWork.deferEvent(event); | |
event = new DescriptorEvent(clone); | |
event.setQuery(query); | |
event.setSession(unitOfWork); | |
event.setDescriptor(this.descriptor); | |
event.setRecord(databaseRow); | |
//bug 259404: ensure postClone is called for objects built directly into the UnitOfWork | |
//in this case, the original is the clone | |
event.setOriginalObject(clone); | |
event.setEventCode(DescriptorEventManager.PostCloneEvent); | |
} | |
unitOfWork.deferEvent(event); | |
} | |
/** | |
* INTERNAL: | |
* Builds a working copy clone directly from the database row. | |
* This is the key method that allows us to execute queries against a | |
* UnitOfWork while in transaction and not cache the results in the shared | |
* cache. This is because we might violate transaction isolation by | |
* putting uncommitted versions of objects in the shared cache. | |
*/ | |
protected Object buildWorkingCopyCloneFromRow(ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, Object primaryKey, CacheKey preFetchedCacheKey) throws DatabaseException, QueryException { | |
ClassDescriptor descriptor = this.descriptor; | |
// If the clone already exists then it may only need to be refreshed or returned. | |
// We call directly on the identity map to avoid going to the parent, | |
// registering if found, and wrapping the result. | |
// Acquire or create the cache key as is need once the object is build anyway. | |
CacheKey unitOfWorkCacheKey = unitOfWork.getIdentityMapAccessorInstance().getIdentityMapManager().acquireLock(primaryKey, descriptor.getJavaClass(), false, descriptor, true); | |
Object workingClone = unitOfWorkCacheKey.getObject(); | |
FetchGroup fetchGroup = query.getExecutionFetchGroup(descriptor); | |
FetchGroupManager fetchGroupManager = descriptor.getFetchGroupManager(); | |
try { | |
// If there is a clone, and it is not a refresh then just return it. | |
boolean wasAClone = workingClone != null; | |
boolean isARefresh = query.shouldRefreshIdentityMapResult() || (query.isLockQuery() && (!wasAClone || !query.isClonePessimisticLocked(workingClone, unitOfWork))); | |
// Also need to refresh if the clone is a partial object and query requires more than its fetch group. | |
if (wasAClone && fetchGroupManager != null && (fetchGroupManager.isPartialObject(workingClone) && (!fetchGroupManager.isObjectValidForFetchGroup(workingClone, fetchGroupManager.getEntityFetchGroup(fetchGroup))))) { | |
isARefresh = true; | |
} | |
if (wasAClone && (!isARefresh)) { | |
return workingClone; | |
} | |
boolean wasAnOriginal = false; | |
boolean isIsolated = descriptor.getCachePolicy().shouldIsolateObjectsInUnitOfWork() | |
|| (descriptor.shouldIsolateObjectsInUnitOfWorkEarlyTransaction() && unitOfWork.wasTransactionBegunPrematurely()); | |
Object original = null; | |
CacheKey originalCacheKey = null; | |
// If not refreshing can get the object from the cache. | |
if ((!isARefresh) && (!isIsolated) && !query.shouldRetrieveBypassCache() && !unitOfWork.shouldReadFromDB() && (!unitOfWork.shouldForceReadFromDB(query, primaryKey))) { | |
AbstractSession session = unitOfWork.getParentIdentityMapSession(query); | |
if (preFetchedCacheKey == null){ | |
originalCacheKey = session.getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, descriptor.getJavaClass(), descriptor, false); | |
}else{ | |
originalCacheKey = preFetchedCacheKey; | |
originalCacheKey.acquireLock(query); | |
} | |
if (originalCacheKey != null) { | |
// PERF: Read-lock is not required on object as unit of work will acquire this on clone and object cannot gc and object identity is maintained. | |
original = originalCacheKey.getObject(); | |
wasAnOriginal = original != null; | |
// If the original is invalid or always refresh then need to refresh. | |
isARefresh = wasAnOriginal && (descriptor.shouldAlwaysRefreshCache() || descriptor.getCacheInvalidationPolicy().isInvalidated(originalCacheKey, query.getExecutionTime())); | |
// Otherwise can just register the cached original object and return it. | |
if (wasAnOriginal && (!isARefresh)){ | |
if (descriptor.getCachePolicy().isSharedIsolation() || !descriptor.shouldIsolateProtectedObjectsInUnitOfWork()) { | |
// using shared isolation and the original is from the shared cache | |
// or using protected isolation and isolated client sessions | |
return unitOfWork.cloneAndRegisterObject(original, originalCacheKey, unitOfWorkCacheKey, descriptor); | |
} | |
} | |
} | |
} | |
if (!wasAClone) { | |
// This code is copied from UnitOfWork.cloneAndRegisterObject. Unlike | |
// that method we don't need to lock the shared cache, because | |
// are not building off of an original in the shared cache. | |
// The copy policy is easier to invoke if we have an original. | |
if (wasAnOriginal && !query.shouldRetrieveBypassCache()) { | |
workingClone = instantiateWorkingCopyClone(original, unitOfWork); | |
// intentionally put nothing in clones to originals, unless really was one. | |
unitOfWork.getCloneToOriginals().put(workingClone, original); | |
} else { | |
if (descriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy() && !databaseRow.hasSopObject()) { | |
// serialized sopObject is a value corresponding to sopField in the row, row.sopObject==null; | |
// the following line sets deserialized sopObject into row.sopObject variable and sets sopField's value to null; | |
workingClone = descriptor.getSerializedObjectPolicy().getObjectFromRow(databaseRow, unitOfWork, (ObjectLevelReadQuery)query); | |
} | |
if (workingClone == null) { | |
// What happens if a copy policy is defined is not pleasant. | |
//workingClone = instantiateWorkingCopyCloneFromRow(databaseRow, query, primaryKey, unitOfWork); | |
// Create a new instance instead. The object is populated later by buildAttributesIntoWorkingCopyClone method. | |
workingClone = buildNewInstance(); | |
} | |
} | |
// This must be registered before it is built to avoid cycles. | |
// The version and read is set below in copyQueryInfoToCacheKey. | |
unitOfWorkCacheKey.setObject(workingClone); | |
// This must be registered before it is built to avoid cycles. | |
unitOfWork.getCloneMapping().put(workingClone, workingClone); | |
} | |
// Must avoid infinite loops while refreshing. | |
if (wasAClone && (unitOfWorkCacheKey.getLastUpdatedQueryId() >= query.getQueryId())) { | |
return workingClone; | |
} | |
copyQueryInfoToCacheKey(unitOfWorkCacheKey, query, databaseRow, unitOfWork, descriptor); | |
ObjectChangePolicy policy = descriptor.getObjectChangePolicy(); | |
// If it was a clone the change listener must be cleared after. | |
if (!wasAClone) { | |
// The change listener must be set before building the clone as aggregate/collections need the listener. | |
policy.setChangeListener(workingClone, unitOfWork, descriptor); | |
} | |
// Turn it 'off' to prevent unwanted events. | |
policy.dissableEventProcessing(workingClone); | |
if (isARefresh && fetchGroupManager != null) { | |
fetchGroupManager.setObjectFetchGroup(workingClone, query.getExecutionFetchGroup(this.descriptor), unitOfWork); | |
} | |
if (!unitOfWork.wasTransactionBegunPrematurely() && descriptor.getCachePolicy().isProtectedIsolation() && !isIsolated && !query.shouldStoreBypassCache()) { | |
// we are at this point because we have isolated protected entities to the UnitOfWork | |
// we should ensure that we populate the cache as well. | |
originalCacheKey = (CacheKey) buildObject(true, query, databaseRow, unitOfWork.getParentIdentityMapSession(descriptor, false, true), primaryKey, preFetchedCacheKey, descriptor, joinManager); | |
} | |
//If we are unable to access the shared cache because of any of the above settings at this point | |
// the cachekey will be null so the attribute building will not be able to access the shared cache. | |
if (isARefresh){ | |
//if we need to refresh the UOW then remove the cache key and the clone will be rebuilt not using any of the | |
//cache. This should be updated to force the buildAttributesIntoWorkingCopyClone to refresh the objects | |
originalCacheKey = null; | |
} | |
// Build/refresh the clone from the row. | |
buildAttributesIntoWorkingCopyClone(workingClone, originalCacheKey, query, joinManager, databaseRow, unitOfWork, wasAClone); | |
// Set fetch group after building object if not a refresh to avoid checking fetch during building. | |
if ((!isARefresh) && fetchGroupManager != null) { | |
fetchGroupManager.setObjectFetchGroup(workingClone, query.getExecutionFetchGroup(this.descriptor), unitOfWork); | |
} | |
Object backupClone = policy.buildBackupClone(workingClone, this, unitOfWork); | |
// If it was a clone the change listener must be cleared. | |
if (wasAClone) { | |
policy.clearChanges(workingClone, unitOfWork, descriptor, isARefresh); | |
} | |
policy.enableEventProcessing(workingClone); | |
unitOfWork.getCloneMapping().put(workingClone, backupClone); | |
query.recordCloneForPessimisticLocking(workingClone, unitOfWork); | |
// PERF: Cache the primary key if implements PersistenceEntity. | |
if (workingClone instanceof PersistenceEntity) { | |
((PersistenceEntity)workingClone)._persistence_setId(primaryKey); | |
} | |
} finally { | |
unitOfWorkCacheKey.release(); | |
} | |
instantiateEagerMappings(workingClone, unitOfWork); | |
return workingClone; | |
} | |
/** | |
* INTERNAL: | |
* Builds a working copy clone directly from a result set. | |
* PERF: This method is optimized for a specific case of building objects | |
* so can avoid many of the normal checks, only queries that have this criteria | |
* can use this method of building objects. | |
*/ | |
public Object buildObjectFromResultSet(ObjectBuildingQuery query, JoinedAttributeManager joinManager, ResultSet resultSet, AbstractSession executionSession, DatabaseAccessor accessor, ResultSetMetaData metaData, DatabasePlatform platform, Vector fieldsList, DatabaseField[] fieldsArray) throws SQLException { | |
ClassDescriptor descriptor = this.descriptor; | |
int pkFieldsSize = descriptor.getPrimaryKeyFields().size(); | |
DatabaseMapping primaryKeyMapping = null; | |
AbstractRecord row = null; | |
Object[] values = null; | |
Object primaryKey; | |
if (isSimple && pkFieldsSize == 1) { | |
primaryKeyMapping = this.primaryKeyMappings.get(0); | |
primaryKey = primaryKeyMapping.valueFromResultSet(resultSet, query, executionSession, accessor, metaData, 1, platform); | |
} else { | |
values = new Object[fieldsArray.length]; | |
row = new ArrayRecord(fieldsList, fieldsArray, values); | |
accessor.populateRow(fieldsArray, values, resultSet, metaData, executionSession, 0, pkFieldsSize); | |
primaryKey = extractPrimaryKeyFromRow(row, executionSession); | |
} | |
UnitOfWorkImpl unitOfWork = null; | |
AbstractSession session = executionSession; | |
boolean isolated = !descriptor.getCachePolicy().isSharedIsolation(); | |
if (session.isUnitOfWork()) { | |
unitOfWork = (UnitOfWorkImpl)executionSession; | |
isolated |= unitOfWork.wasTransactionBegunPrematurely() && descriptor.shouldIsolateObjectsInUnitOfWorkEarlyTransaction(); | |
} | |
CacheKey cacheKey = session.getIdentityMapAccessorInstance().getIdentityMapManager().acquireLock(primaryKey, descriptor.getJavaClass(), false, descriptor, query.isCacheCheckComplete()); | |
CacheKey cacheKeyToUse = cacheKey; | |
CacheKey parentCacheKey = null; | |
Object object = cacheKey.getObject(); | |
try { | |
// Found locally in the unit of work, or session query and found in the session. | |
if (object != null) { | |
return object; | |
} | |
if ((unitOfWork != null) && !isolated) { | |
// Need to lookup in the session. | |
session = unitOfWork.getParentIdentityMapSession(query); | |
parentCacheKey = session.getIdentityMapAccessorInstance().getIdentityMapManager().acquireLock(primaryKey, descriptor.getJavaClass(), false, descriptor, query.isCacheCheckComplete()); | |
cacheKeyToUse = parentCacheKey; | |
object = parentCacheKey.getObject(); | |
} | |
// If the object is not in the cache, it needs to be built, this is building in the unit of work if isolated. | |
if (object == null) { | |
object = buildNewInstance(); | |
if (unitOfWork == null) { | |
cacheKey.setObject(object); | |
} else { | |
if (isolated) { | |
cacheKey.setObject(object); | |
unitOfWork.getCloneMapping().put(object, object); | |
} else { | |
parentCacheKey.setObject(object); | |
} | |
} | |
List mappings = descriptor.getMappings(); | |
int size = mappings.size(); | |
if (isSimple) { | |
int shift = descriptor.getTables().size() * pkFieldsSize; | |
if (primaryKeyMapping != null) { | |
// simple primary key - set pk directly through the mapping | |
primaryKeyMapping.setAttributeValueInObject(object, primaryKey); | |
} else { | |
// composite primary key - set pk using pkRow | |
boolean isTargetProtected = session.isProtectedSession(); | |
for (int index = 0; index < pkFieldsSize; index++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); | |
mapping.readFromRowIntoObject(row, joinManager, object, cacheKeyToUse, query, session, isTargetProtected); | |
} | |
} | |
// set the rest using mappings directly | |
for (int index = pkFieldsSize; index < size; index++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); | |
mapping.readFromResultSetIntoObject(resultSet, object, query, session, accessor, metaData, index + shift, platform); | |
} | |
} else { | |
boolean isTargetProtected = session.isProtectedSession(); | |
accessor.populateRow(fieldsArray, values, resultSet, metaData, session, pkFieldsSize, fieldsArray.length); | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); | |
mapping.readFromRowIntoObject(row, joinManager, object, cacheKeyToUse, query, session, isTargetProtected); | |
} | |
} | |
((PersistenceEntity)object)._persistence_setId(primaryKey); | |
if ((unitOfWork != null) && isolated) { | |
ObjectChangePolicy policy = descriptor.getObjectChangePolicy(); | |
policy.setChangeListener(object, unitOfWork, descriptor); | |
} | |
} | |
if ((unitOfWork != null) && !isolated) { | |
// Need to clone the object in the unit of work. | |
// TODO: Doesn't work all the time | |
// With one setup (jpa2.performance tests) produces a shallow clone (which is good enough for isSimple==true case only), | |
// in other (jpa.advanced tests) - just a brand new empty object. | |
Object clone = instantiateWorkingCopyClone(object, unitOfWork); | |
((PersistenceEntity)clone)._persistence_setId(cacheKey.getKey()); | |
unitOfWork.getCloneMapping().put(clone, clone); | |
unitOfWork.getCloneToOriginals().put(clone, object); | |
cacheKey.setObject(clone); | |
ObjectChangePolicy policy = descriptor.getObjectChangePolicy(); | |
policy.setChangeListener(clone, unitOfWork, descriptor); | |
object = clone; | |
} | |
} finally { | |
cacheKey.release(); | |
if (parentCacheKey != null) { | |
parentCacheKey.release(); | |
} | |
} | |
return object; | |
} | |
/** | |
* Returns a clone of itself. | |
*/ | |
public Object clone() { | |
ObjectBuilder objectBuilder = null; | |
try { | |
objectBuilder = (ObjectBuilder)super.clone(); | |
} catch (CloneNotSupportedException exception) { | |
throw new InternalError(exception.toString()); | |
} | |
// Only the shallow copy is created. The entries never change in these data structures | |
objectBuilder.setMappingsByAttribute(new HashMap(getMappingsByAttribute())); | |
objectBuilder.setMappingsByField(new HashMap(getMappingsByField())); | |
objectBuilder.setFieldsMap(new HashMap(getFieldsMap())); | |
objectBuilder.setReadOnlyMappingsByField(new HashMap(getReadOnlyMappingsByField())); | |
objectBuilder.setPrimaryKeyMappings(new ArrayList(getPrimaryKeyMappings())); | |
if (nonPrimaryKeyMappings != null) { | |
objectBuilder.setNonPrimaryKeyMappings(new ArrayList(getNonPrimaryKeyMappings())); | |
} | |
objectBuilder.cloningMappings = new ArrayList(this.cloningMappings); | |
objectBuilder.eagerMappings = new ArrayList(this.eagerMappings); | |
objectBuilder.relationshipMappings = new ArrayList(this.relationshipMappings); | |
return objectBuilder; | |
} | |
/** | |
* INTERNAL: | |
* This method is used by the UnitOfWork to cascade registration of new objects. | |
* It may raise exceptions as described in the EJB3 specification | |
*/ | |
public void cascadePerformRemove(Object object, UnitOfWorkImpl uow, Map visitedObjects) { | |
// PERF: Only process relationships. | |
if (!this.isSimple) { | |
List<DatabaseMapping> mappings = this.relationshipMappings; | |
for (int index = 0; index < mappings.size(); index++) { | |
DatabaseMapping mapping = mappings.get(index); | |
mapping.cascadePerformRemoveIfRequired(object, uow, visitedObjects); | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* This method is used to iterate over the specified object's mappings and cascade | |
* remove orphaned private owned objects from the UnitOfWorkChangeSet and IdentityMap. | |
*/ | |
public void cascadePerformRemovePrivateOwnedObjectFromChangeSet(Object object, UnitOfWorkImpl uow, Map visitedObjects) { | |
if (object != null && !this.isSimple) { | |
for (DatabaseMapping mapping : this.relationshipMappings) { | |
// only cascade into private owned mappings | |
if (mapping.isPrivateOwned()) { | |
mapping.cascadePerformRemovePrivateOwnedObjectFromChangeSetIfRequired(object, uow, visitedObjects); | |
} | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* This method is used to store the FK values used for this mapping in the cachekey. | |
* This is used when the mapping is protected but we have retrieved the fk values and will cache | |
* them for use when the entity is cloned. | |
*/ | |
public void cacheForeignKeyValues(AbstractRecord databaseRecord, CacheKey cacheKey, AbstractSession session) { | |
Set<DatabaseField> foreignKeys = this.descriptor.getForeignKeyValuesForCaching(); | |
if (foreignKeys.isEmpty()) { | |
return; | |
} | |
DatabaseRecord cacheRecord = new DatabaseRecord(foreignKeys.size()); | |
for (DatabaseField field : foreignKeys) { | |
cacheRecord.put(field, databaseRecord.get(field)); | |
} | |
cacheKey.setProtectedForeignKeys(cacheRecord); | |
} | |
/** | |
* INTERNAL: | |
* This method is used to store the FK values used for this mapping in the cachekey. | |
* This is used when the mapping is protected but we have retrieved the fk values and will cache | |
* them for use when the entity is cloned. | |
*/ | |
public void cacheForeignKeyValues(Object source, CacheKey cacheKey, ClassDescriptor descriptor, AbstractSession session) { | |
Set<DatabaseField> foreignKeys = this.descriptor.getForeignKeyValuesForCaching(); | |
if (foreignKeys.isEmpty()) { | |
return; | |
} | |
DatabaseRecord cacheRecord = new DatabaseRecord(foreignKeys.size()); | |
for (DatabaseField field : foreignKeys) { | |
cacheRecord.put(field, extractValueFromObjectForField(source, field, session)); | |
} | |
cacheKey.setProtectedForeignKeys(cacheRecord); | |
} | |
/** | |
* INTERNAL: | |
* Cascade discover and persist new objects during commit. | |
* It may raise exceptions as described in the EJB3 specification | |
*/ | |
public void cascadeDiscoverAndPersistUnregisteredNewObjects(Object object, Map newObjects, Map unregisteredExistingObjects, Map visitedObjects, UnitOfWorkImpl uow, Set cascadeErrors) { | |
// PERF: Only process relationships. | |
if (!this.isSimple) { | |
List<DatabaseMapping> mappings = this.relationshipMappings; | |
int size = mappings.size(); | |
FetchGroupManager fetchGroupManager = descriptor.getFetchGroupManager(); | |
// Only cascade fetched mappings. | |
if ((fetchGroupManager != null) && fetchGroupManager.isPartialObject(object)) { | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = mappings.get(index); | |
if (fetchGroupManager.isAttributeFetched(object, mapping.getAttributeName())) { | |
mapping.cascadeDiscoverAndPersistUnregisteredNewObjects(object, newObjects, unregisteredExistingObjects, visitedObjects, uow, cascadeErrors); | |
} | |
} | |
} else { | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = mappings.get(index); | |
mapping.cascadeDiscoverAndPersistUnregisteredNewObjects(object, newObjects, unregisteredExistingObjects, visitedObjects, uow, cascadeErrors); | |
} | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* This method is used by the UnitOfWork to cascade registration of new objects. | |
* It may raise exceptions as described in the EJB3 specification | |
*/ | |
public void cascadeRegisterNewForCreate(Object object, UnitOfWorkImpl uow, Map visitedObjects) { | |
// PERF: Only process relationships. | |
if (!this.isSimple) { | |
List<DatabaseMapping> mappings = this.relationshipMappings; | |
int size = mappings.size(); | |
FetchGroupManager fetchGroupManager = this.descriptor.getFetchGroupManager(); | |
// Only cascade fetched mappings. | |
if ((fetchGroupManager != null) && fetchGroupManager.isPartialObject(object)) { | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = mappings.get(index); | |
if (fetchGroupManager.isAttributeFetched(object, mapping.getAttributeName())) { | |
mapping.cascadeRegisterNewIfRequired(object, uow, visitedObjects); | |
} | |
} | |
} else { | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = mappings.get(index); | |
mapping.cascadeRegisterNewIfRequired(object, uow, visitedObjects); | |
} | |
} | |
} | |
// Allow persist to set the partitioning connection. | |
if (this.descriptor.getPartitioningPolicy() != null) { | |
this.descriptor.getPartitioningPolicy().partitionPersist(uow.getParent(), object, this.descriptor); | |
} | |
} | |
/** | |
* INTERNAL: | |
* This method creates a records change set for a particular object. | |
* It should only be used by aggregates. | |
* @return ObjectChangeSet | |
*/ | |
public ObjectChangeSet compareForChange(Object clone, Object backUp, UnitOfWorkChangeSet changeSet, AbstractSession session) { | |
// delegate the change comparison to this objects ObjectChangePolicy - TGW | |
return descriptor.getObjectChangePolicy().calculateChanges(clone, backUp, backUp == null, changeSet, ((UnitOfWorkImpl)session), this.descriptor, true); | |
} | |
/** | |
* Compares the two specified objects | |
*/ | |
public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) { | |
// PERF: Avoid iterator. | |
List mappings = this.descriptor.getMappings(); | |
for (int index = 0; index < mappings.size(); index++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); | |
if (!mapping.compareObjects(firstObject, secondObject, session)) { | |
Object firstValue = mapping.getAttributeValueFromObject(firstObject); | |
Object secondValue = mapping.getAttributeValueFromObject(secondObject); | |
session.log(SessionLog.FINEST, SessionLog.QUERY, "compare_failed", mapping, firstValue, secondValue); | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* Copy each attribute from one object into the other. | |
*/ | |
public void copyInto(Object source, Object target, boolean cloneOneToOneValueHolders) { | |
// PERF: Avoid iterator. | |
List mappings = this.descriptor.getMappings(); | |
for (int index = 0; index < mappings.size(); index++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); | |
Object value = null; | |
if (cloneOneToOneValueHolders && mapping.isForeignReferenceMapping()){ | |
value = ((ForeignReferenceMapping)mapping).getAttributeValueWithClonedValueHolders(source); | |
} else { | |
value = mapping.getAttributeValueFromObject(source); | |
} | |
mapping.setAttributeValueInObject(target, value); | |
} | |
} | |
/** | |
* Copy each attribute from one object into the other. | |
*/ | |
public void copyInto(Object source, Object target) { | |
copyInto(source, target, false); | |
} | |
/** | |
* Return a copy of the object. | |
* This is NOT used for unit of work but for templatizing an object. | |
* The depth and primary key reseting are passed in. | |
*/ | |
public Object copyObject(Object original, CopyGroup copyGroup) { | |
Object copy = copyGroup.getCopies().get(original); | |
if (copyGroup.shouldCascadeTree()) { | |
FetchGroupManager fetchGroupManager = this.descriptor.getFetchGroupManager(); | |
if (fetchGroupManager != null) { | |
// empty copy group means all the attributes should be copied - don't alter it. | |
if (copyGroup.hasItems()) { | |
// by default add primary key attribute(s) if not already in the group | |
if (!copyGroup.shouldResetPrimaryKey()) { | |
for (DatabaseMapping mapping : this.primaryKeyMappings) { | |
String name = mapping.getAttributeName(); | |
if (!copyGroup.containsAttributeInternal(name)) { | |
copyGroup.addAttribute(name); | |
} | |
} | |
} else { | |
for (DatabaseMapping mapping : this.primaryKeyMappings) { | |
if (mapping.isForeignReferenceMapping()) { | |
String name = mapping.getAttributeName(); | |
if (!copyGroup.containsAttributeInternal(name)) { | |
copyGroup.addAttribute(name); | |
} | |
} | |
} | |
} | |
// by default version attribute if not already in the group | |
if (!copyGroup.shouldResetVersion()) { | |
if (this.lockAttribute != null) { | |
if (!copyGroup.containsAttributeInternal(this.lockAttribute)) { | |
copyGroup.addAttribute(this.lockAttribute); | |
} | |
} | |
} | |
FetchGroup fetchGroup = fetchGroupManager.getObjectFetchGroup(original); | |
if (fetchGroup != null) { | |
if (!fetchGroup.getAttributeNames().containsAll(copyGroup.getAttributeNames())) { | |
// trigger fetch group if it does not contain all attributes of the copy group. | |
fetchGroup.onUnfetchedAttribute((FetchGroupTracker)original, null); | |
} | |
} | |
} | |
// Entity fetch group currently set on copyObject | |
EntityFetchGroup existingEntityFetchGroup = null; | |
if (copy != null) { | |
Object[] copyArray = (Object[])copy; | |
// copy of the original | |
copy = copyArray[0]; | |
// A set of CopyGroups that have visited. | |
Set<CopyGroup> visitedCopyGroups = (Set<CopyGroup>)copyArray[1]; | |
if(visitedCopyGroups.contains(copyGroup)) { | |
// original has been already visited with this copyGroup - leave | |
return copy; | |
} else { | |
visitedCopyGroups.add(copyGroup); | |
} | |
existingEntityFetchGroup = fetchGroupManager.getObjectEntityFetchGroup(copy); | |
} | |
// Entity fetch group that will be assigned to copyObject | |
EntityFetchGroup newEntityFetchGroup = null; | |
// Attributes to be visited - only reference mappings will be visited. | |
// If null then all attributes should be visited. | |
Set<String> attributesToVisit = copyGroup.getAttributeNames(); | |
// Attributes to be copied | |
Set<String> attributesToCopy = attributesToVisit; | |
boolean shouldCopyAllAttributes = false; | |
boolean shouldAssignNewEntityFetchGroup = false; | |
if(copy != null && existingEntityFetchGroup == null) { | |
// all attributes have been already copied | |
attributesToCopy = null; | |
} else { | |
// Entity fetch group corresponding to copyPolicy. | |
// Note that empty, or null, or containing all arguments attributesToCopy | |
// results in copyGroupFetchGroup = null; | |
EntityFetchGroup copyGroupEntityFetchGroup = fetchGroupManager.getEntityFetchGroup(attributesToCopy); | |
if(copyGroupEntityFetchGroup == null) { | |
// all attributes will be copied | |
shouldCopyAllAttributes = true; | |
} | |
if(copy != null) { | |
if(copyGroupEntityFetchGroup != null) { | |
if(!copyGroup.shouldResetPrimaryKey()) { | |
if(!existingEntityFetchGroup.getAttributeNames().containsAll(attributesToCopy)) { | |
// Entity fetch group that will be assigned to copy object | |
newEntityFetchGroup = fetchGroupManager.flatUnionFetchGroups(existingEntityFetchGroup, copyGroupEntityFetchGroup, false); | |
shouldAssignNewEntityFetchGroup = true; | |
} | |
} | |
attributesToCopy = new HashSet(attributesToCopy); | |
attributesToCopy.removeAll(existingEntityFetchGroup.getAttributeNames()); | |
} | |
} else { | |
// copy does not exist - create it | |
copy = copyGroup.getSession().getDescriptor(original).getObjectBuilder().buildNewInstance(); | |
Set<CopyGroup> visitedCopyGroups = new HashSet(); | |
visitedCopyGroups.add(copyGroup); | |
copyGroup.getCopies().put(original, new Object[]{copy, visitedCopyGroups}); | |
if(!copyGroup.shouldResetPrimaryKey()) { | |
newEntityFetchGroup = copyGroupEntityFetchGroup; | |
shouldAssignNewEntityFetchGroup = true; | |
} | |
} | |
} | |
if(shouldAssignNewEntityFetchGroup) { | |
fetchGroupManager.setObjectFetchGroup(copy, newEntityFetchGroup, null); | |
} | |
for (DatabaseMapping mapping : getDescriptor().getMappings()) { | |
String name = mapping.getAttributeName(); | |
boolean shouldCopy = shouldCopyAllAttributes || (attributesToCopy != null && attributesToCopy.contains(name)); | |
boolean shouldVisit = attributesToVisit == null || attributesToVisit.contains(name); | |
if(shouldCopy || shouldVisit) { | |
boolean isVisiting = false; | |
// unless it's a reference mapping pass copyGroup - just to carry the session. | |
CopyGroup mappingCopyGroup = copyGroup; | |
if(mapping.isForeignReferenceMapping()) { | |
ForeignReferenceMapping frMapping = (ForeignReferenceMapping)mapping; | |
ClassDescriptor referenceDescriptor = frMapping.getReferenceDescriptor(); | |
if(referenceDescriptor != null) { | |
isVisiting = true; | |
mappingCopyGroup = copyGroup.getGroup(name); | |
if(mappingCopyGroup == null) { | |
FetchGroupManager referenceFetchGroupManager = referenceDescriptor.getFetchGroupManager(); | |
if(referenceFetchGroupManager != null) { | |
EntityFetchGroup nonReferenceEntityFetchGroup = referenceFetchGroupManager.getNonReferenceEntityFetchGroup(copyGroup.shouldResetPrimaryKey(), copyGroup.shouldResetVersion()); | |
if(nonReferenceEntityFetchGroup != null) { | |
mappingCopyGroup = nonReferenceEntityFetchGroup.toCopyGroup(); | |
} else { | |
// null nonReferenceEntityFetchGroup is equivalent to containing all attributes: | |
// create a new empty CopyGroup. | |
mappingCopyGroup = new CopyGroup(); | |
mappingCopyGroup.shouldCascadeTree(); | |
} | |
} else { | |
// TODO: would that work? | |
mappingCopyGroup = new CopyGroup(); | |
mappingCopyGroup.dontCascade(); | |
isVisiting = false; | |
} | |
mappingCopyGroup.setCopies(copyGroup.getCopies()); | |
mappingCopyGroup.setShouldResetPrimaryKey(copyGroup.shouldResetPrimaryKey()); | |
mappingCopyGroup.setShouldResetVersion(copyGroup.shouldResetVersion()); | |
} | |
mappingCopyGroup.setSession(copyGroup.getSession()); | |
} | |
} else if (mapping.isAggregateObjectMapping()) { | |
mappingCopyGroup = new CopyGroup(); | |
} | |
if(shouldCopy || isVisiting) { | |
// TODO: optimization: (even when isVisiting == true) redefine buildCopy to take shouldCopy and don't copy if not required. | |
mapping.buildCopy(copy, original, mappingCopyGroup); | |
} | |
} | |
} | |
} else { | |
// fetchGroupManager == null | |
// TODO | |
} | |
} else { | |
// ! copyGroup.shouldCascadeTree() | |
if (copy != null) { | |
return copy; | |
} | |
copy = instantiateClone(original, copyGroup.getSession()); | |
copyGroup.getCopies().put(original, copy); | |
// PERF: Avoid synchronized enumerator as is concurrency bottleneck. | |
List mappings = getCloningMappings(); | |
int size = mappings.size(); | |
for (int index = 0; index < size; index++) { | |
((DatabaseMapping)mappings.get(index)).buildCopy(copy, original, copyGroup); | |
} | |
if (copyGroup.shouldResetPrimaryKey() && (!(this.descriptor.isDescriptorTypeAggregate()))) { | |
// Do not reset if any of the keys is mapped through a 1-1, i.e. back reference id has already changed. | |
boolean hasOneToOne = false; | |
List primaryKeyMappings = getPrimaryKeyMappings(); | |
size = primaryKeyMappings.size(); | |
for (int index = 0; index < size; index++) { | |
if (((DatabaseMapping)primaryKeyMappings.get(index)).isOneToOneMapping()) { | |
hasOneToOne = true; | |
} | |
} | |
if (!hasOneToOne) { | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = (DatabaseMapping)primaryKeyMappings.get(index); | |
// Only null out direct mappings, as others will be nulled in the respective objects. | |
if (mapping.isAbstractColumnMapping()) { | |
Object nullValue = ((AbstractColumnMapping)mapping).getObjectValue(null, copyGroup.getSession()); | |
mapping.setAttributeValueInObject(copy, nullValue); | |
} else if (mapping.isTransformationMapping()) { | |
mapping.setAttributeValueInObject(copy, null); | |
} | |
} | |
} | |
} | |
// PERF: Avoid events if no listeners. | |
if (this.descriptor.getEventManager().hasAnyEventListeners()) { | |
org.eclipse.persistence.descriptors.DescriptorEvent event = new org.eclipse.persistence.descriptors.DescriptorEvent(copy); | |
event.setSession(copyGroup.getSession()); | |
event.setOriginalObject(original); | |
event.setEventCode(DescriptorEventManager.PostCloneEvent); | |
this.descriptor.getEventManager().executeEvent(event); | |
} | |
} | |
return copy; | |
} | |
/** | |
* INTERNAL: | |
* Used by the ObjectBuilder to create an ObjectChangeSet for the specified clone object. | |
* @return ObjectChangeSet the newly created changeSet representing the clone object | |
* @param clone the object to convert to a changeSet. | |
* @param uowChangeSet the owner of this changeSet. | |
*/ | |
public ObjectChangeSet createObjectChangeSet(Object clone, UnitOfWorkChangeSet uowChangeSet, AbstractSession session) { | |
boolean isNew = ((UnitOfWorkImpl)session).isCloneNewObject(clone); | |
return createObjectChangeSet(clone, uowChangeSet, isNew, session); | |
} | |
/** | |
* INTERNAL: | |
* Used by the ObjectBuilder to create an ObjectChangeSet for the specified clone object. | |
* @return ObjectChangeSet the newly created changeSet representing the clone object | |
* @param clone the object to convert to a changeSet. | |
* @param uowChangeSet the owner of this changeSet. | |
* @param isNew signifies if the clone object is a new object. | |
*/ | |
public ObjectChangeSet createObjectChangeSet(Object clone, UnitOfWorkChangeSet uowChangeSet, boolean isNew, AbstractSession session) { | |
return createObjectChangeSet(clone, uowChangeSet, isNew, false, session); | |
} | |
/** | |
* INTERNAL: | |
* Used by the ObjectBuilder to create an ObjectChangeSet for the specified clone object. | |
* @return ObjectChangeSet the newly created changeSet representing the clone object | |
* @param clone the object to convert to a changeSet. | |
* @param uowChangeSet the owner of this changeSet. | |
* @param isNew signifies if the clone object is a new object. | |
* @param assignPrimaryKeyIfExisting signifies if the primary key of the change set should be updated if existing. | |
*/ | |
public ObjectChangeSet createObjectChangeSet(Object clone, UnitOfWorkChangeSet uowChangeSet, boolean isNew, boolean assignPrimaryKeyIfExisting, AbstractSession session) { | |
ObjectChangeSet changes = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(clone); | |
if (changes == null) { | |
if (this.descriptor.isAggregateDescriptor()) { | |
changes = new AggregateObjectChangeSet(CacheId.EMPTY, this.descriptor, clone, uowChangeSet, isNew); | |
} else { | |
changes = new ObjectChangeSet(extractPrimaryKeyFromObject(clone, session, true), this.descriptor, clone, uowChangeSet, isNew); | |
} | |
changes.setIsAggregate(this.descriptor.isDescriptorTypeAggregate()); | |
uowChangeSet.addObjectChangeSetForIdentity(changes, clone); | |
} else{ | |
if (isNew && !changes.isNew()) { | |
//this is an unregistered new object that we found during change calc | |
//or change listener update. Let's switch it to be new. | |
changes.setIsNew(isNew); | |
} | |
if (assignPrimaryKeyIfExisting) { | |
if (!changes.isAggregate()) { | |
// If creating a new change set for a new object, the original change set (from change tracking) may have not had the primary key. | |
Object primaryKey = extractPrimaryKeyFromObject(clone, session, true); | |
if (primaryKey != null) { | |
changes.setId(primaryKey); | |
} | |
} | |
} | |
} | |
return changes; | |
} | |
/** | |
* Creates and stores primary key expression. | |
*/ | |
public void createPrimaryKeyExpression(AbstractSession session) { | |
Expression expression = null; | |
Expression builder = new ExpressionBuilder(); | |
Expression subExp1; | |
Expression subExp2; | |
Expression subExpression; | |
List primaryKeyFields = this.descriptor.getPrimaryKeyFields(); | |
if(null != primaryKeyFields) { | |
for (int index = 0; index < primaryKeyFields.size(); index++) { | |
DatabaseField primaryKeyField = (DatabaseField)primaryKeyFields.get(index); | |
subExp1 = builder.getField(primaryKeyField); | |
subExp2 = builder.getParameter(primaryKeyField); | |
subExpression = subExp1.equal(subExp2); | |
if (expression == null) { | |
expression = subExpression; | |
} else { | |
expression = expression.and(subExpression); | |
} | |
} | |
} | |
setPrimaryKeyExpression(expression); | |
} | |
/** | |
* Return the row with primary keys and their values from the given expression. | |
*/ | |
public Object extractPrimaryKeyFromExpression(boolean requiresExactMatch, Expression expression, AbstractRecord translationRow, AbstractSession session) { | |
AbstractRecord primaryKeyRow = createRecord(getPrimaryKeyMappings().size(), session); | |
expression.getBuilder().setSession(session.getRootSession(null)); | |
// Get all the field & values from expression. | |
boolean isValid = expression.extractPrimaryKeyValues(requiresExactMatch, this.descriptor, primaryKeyRow, translationRow); | |
if (requiresExactMatch && (!isValid)) { | |
return null; | |
} | |
// Check that the sizes match. | |
if (primaryKeyRow.size() != this.descriptor.getPrimaryKeyFields().size()) { | |
return null; | |
} | |
Object primaryKey = extractPrimaryKeyFromRow(primaryKeyRow, session); | |
if ((primaryKey == null) && isValid) { | |
return InvalidObject.instance; | |
} | |
return primaryKey; | |
} | |
/** | |
* Return if the expression is by primary key. | |
*/ | |
public boolean isPrimaryKeyExpression(boolean requiresExactMatch, Expression expression, AbstractSession session) { | |
expression.getBuilder().setSession(session.getRootSession(null)); | |
List<DatabaseField> keyFields = this.descriptor.getPrimaryKeyFields(); | |
int size = keyFields.size(); | |
Set<DatabaseField> fields = new HashSet(size); | |
boolean isValid = expression.extractFields(requiresExactMatch, true, this.descriptor, keyFields, fields); | |
if (requiresExactMatch && (!isValid)) { | |
return false; | |
} | |
// Check that the sizes match. | |
if (fields.size() != size) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Extract primary key attribute values from the domainObject. | |
*/ | |
@Override | |
public Object extractPrimaryKeyFromObject(Object domainObject, AbstractSession session) { | |
return extractPrimaryKeyFromObject(domainObject, session, false); | |
} | |
/** | |
* Extract primary key attribute values from the domainObject. | |
*/ | |
public Object extractPrimaryKeyFromObject(Object domainObject, AbstractSession session, boolean shouldReturnNullIfNull) { | |
if (domainObject == null) { | |
return null; | |
} | |
// Avoid using the cached id for XML, as the relational descriptor may be different than the xml one. | |
boolean isPersistenceEntity = (domainObject instanceof PersistenceEntity) && (!isXMLObjectBuilder()); | |
if (isPersistenceEntity) { | |
Object primaryKey = ((PersistenceEntity)domainObject)._persistence_getId(); | |
if (primaryKey != null) { | |
return primaryKey; | |
} | |
} | |
ClassDescriptor descriptor = this.descriptor; | |
boolean isNull = false; | |
// Allow for inheritance, the concrete descriptor must always be used. | |
if (descriptor.hasInheritance() && (domainObject.getClass() != descriptor.getJavaClass()) && (!domainObject.getClass().getSuperclass().equals(descriptor.getJavaClass()))) { | |
return session.getDescriptor(domainObject).getObjectBuilder().extractPrimaryKeyFromObject(domainObject, session, shouldReturnNullIfNull); | |
} | |
CacheKeyType cacheKeyType = descriptor.getCachePolicy().getCacheKeyType(); | |
List<DatabaseField> primaryKeyFields = descriptor.getPrimaryKeyFields(); | |
Object[] primaryKeyValues = null; | |
if (cacheKeyType != CacheKeyType.ID_VALUE) { | |
primaryKeyValues = new Object[primaryKeyFields.size()]; | |
} | |
List<DatabaseMapping> mappings = getPrimaryKeyMappings(); | |
int size = mappings.size(); | |
// PERF: optimize simple case of direct mapped singleton primary key. | |
if (descriptor.hasSimplePrimaryKey()) { | |
// PERF: use index not enumeration. | |
for (int index = 0; index < size; index++) { | |
AbstractColumnMapping mapping = (AbstractColumnMapping)mappings.get(index); | |
Object keyValue = mapping.valueFromObject(domainObject, primaryKeyFields.get(index), session); | |
if (isPrimaryKeyComponentInvalid(keyValue, index)) { | |
if (shouldReturnNullIfNull) { | |
return null; | |
} | |
isNull = true; | |
} | |
if (cacheKeyType == CacheKeyType.ID_VALUE) { | |
if (isPersistenceEntity && (!isNull)) { | |
((PersistenceEntity)domainObject)._persistence_setId(keyValue); | |
} | |
return keyValue; | |
} else { | |
primaryKeyValues[index] = keyValue; | |
} | |
} | |
} else { | |
AbstractRecord databaseRow = createRecordForPKExtraction(size, session); | |
// PERF: use index not enumeration | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = mappings.get(index); | |
// Primary key mapping may be null for aggregate collection. | |
if (mapping != null) { | |
mapping.writeFromObjectIntoRow(domainObject, databaseRow, session, WriteType.UNDEFINED); | |
} | |
} | |
List<Class> primaryKeyClassifications = getPrimaryKeyClassifications(); | |
Platform platform = session.getPlatform(domainObject.getClass()); | |
// PERF: use index not enumeration | |
for (int index = 0; index < size; index++) { | |
// Ensure that the type extracted from the object is the same type as in the descriptor, | |
// the main reason for this is that 1-1 can optimize on vh by getting from the row as the row-type. | |
Class classification = primaryKeyClassifications.get(index); | |
Object value = databaseRow.get(primaryKeyFields.get(index)); | |
if (isPrimaryKeyComponentInvalid(value, index)) { | |
if (shouldReturnNullIfNull) { | |
return null; | |
} | |
isNull = true; | |
} | |
value = platform.convertObject(value, classification); | |
if (cacheKeyType == CacheKeyType.ID_VALUE) { | |
if (isPersistenceEntity && (!isNull)) { | |
((PersistenceEntity)domainObject)._persistence_setId(value); | |
} | |
return value; | |
} else { | |
primaryKeyValues[index] = value; | |
} | |
} | |
} | |
CacheId id = new CacheId(primaryKeyValues); | |
if (isPersistenceEntity && (!isNull)) { | |
((PersistenceEntity)domainObject)._persistence_setId(id); | |
} | |
return id; | |
} | |
/** | |
* Extract primary key values from the specified row. | |
* null is returned if the row does not contain the key. | |
*/ | |
public Object extractPrimaryKeyFromRow(AbstractRecord databaseRow, AbstractSession session) { | |
if (databaseRow.hasSopObject()) { | |
// Entity referencing ForeignReferenceMapping has set attribute extracted from sopObject as a sopObject into a new empty row. | |
return extractPrimaryKeyFromObject(databaseRow.getSopObject(), session); | |
} | |
List<DatabaseField> primaryKeyFields = this.descriptor.getPrimaryKeyFields(); | |
if(null == primaryKeyFields) { | |
return null; | |
} | |
List<Class> primaryKeyClassifications = getPrimaryKeyClassifications(); | |
int size = primaryKeyFields.size(); | |
Object[] primaryKeyValues = null; | |
CacheKeyType cacheKeyType = this.descriptor.getCachePolicy().getCacheKeyType(); | |
if (cacheKeyType != CacheKeyType.ID_VALUE) { | |
primaryKeyValues = new Object[size]; | |
} | |
int numberOfNulls = 0; | |
// PERF: use index not enumeration | |
for (int index = 0; index < size; index++) { | |
DatabaseField field = primaryKeyFields.get(index); | |
// Ensure that the type extracted from the row is the same type as in the object. | |
Class classification = primaryKeyClassifications.get(index); | |
Object value = databaseRow.get(field); | |
if (value != null) { | |
if (value.getClass() != classification) { | |
value = session.getPlatform(this.descriptor.getJavaClass()).convertObject(value, classification); | |
} | |
if (cacheKeyType == CacheKeyType.ID_VALUE) { | |
return value; | |
} | |
primaryKeyValues[index] = value; | |
} else { | |
if (this.mayHaveNullInPrimaryKey) { | |
numberOfNulls++; | |
if (numberOfNulls < size) { | |
primaryKeyValues[index] = null; | |
} else { | |
// Must have some non null elements. If all elements are null return null. | |
return null; | |
} | |
} else { | |
return null; | |
} | |
} | |
} | |
return new CacheId(primaryKeyValues); | |
} | |
/** | |
* Return the row with primary keys and their values from the given expression. | |
*/ | |
public AbstractRecord extractPrimaryKeyRowFromExpression(Expression expression, AbstractRecord translationRow, AbstractSession session) { | |
if (translationRow != null && translationRow.hasSopObject()) { | |
return translationRow; | |
} | |
AbstractRecord primaryKeyRow = createRecord(getPrimaryKeyMappings().size(), session); | |
expression.getBuilder().setSession(session.getRootSession(null)); | |
// Get all the field & values from expression | |
boolean isValid = expression.extractPrimaryKeyValues(true, this.descriptor, primaryKeyRow, translationRow); | |
if (!isValid) { | |
return null; | |
} | |
// Check that the sizes match up | |
if (primaryKeyRow.size() != this.descriptor.getPrimaryKeyFields().size()) { | |
return null; | |
} | |
return primaryKeyRow; | |
} | |
/** | |
* Return the row from the given expression. | |
*/ | |
public AbstractRecord extractRowFromExpression(Expression expression, AbstractRecord translationRow, AbstractSession session) { | |
AbstractRecord record = createRecord(session); | |
expression.getBuilder().setSession(session.getRootSession(null)); | |
// Get all the field & values from expression | |
boolean isValid = expression.extractValues(false, false, this.descriptor, record, translationRow); | |
if (!isValid) { | |
return null; | |
} | |
return record; | |
} | |
/** | |
* Extract primary key attribute values from the domainObject. | |
*/ | |
public AbstractRecord extractPrimaryKeyRowFromObject(Object domainObject, AbstractSession session) { | |
AbstractRecord databaseRow = createRecord(getPrimaryKeyMappings().size(), session); | |
// PERF: use index not enumeration. | |
for (int index = 0; index < getPrimaryKeyMappings().size(); index++) { | |
getPrimaryKeyMappings().get(index).writeFromObjectIntoRow(domainObject, databaseRow, session, WriteType.UNDEFINED); | |
} | |
// PERF: optimize simple primary key case, no need to remap. | |
if (this.descriptor.hasSimplePrimaryKey()) { | |
return databaseRow; | |
} | |
AbstractRecord primaryKeyRow = createRecord(getPrimaryKeyMappings().size(), session); | |
List primaryKeyFields = this.descriptor.getPrimaryKeyFields(); | |
for (int index = 0; index < primaryKeyFields.size(); index++) { | |
// Ensure that the type extracted from the object is the same type as in the descriptor, | |
// the main reason for this is that 1-1 can optimize on vh by getting from the row as the row-type. | |
Class classification = getPrimaryKeyClassifications().get(index); | |
DatabaseField field = (DatabaseField)primaryKeyFields.get(index); | |
Object value = databaseRow.get(field); | |
primaryKeyRow.put(field, session.getPlatform(domainObject.getClass()).convertObject(value, classification)); | |
} | |
return primaryKeyRow; | |
} | |
/** | |
* Extract the value of the primary key attribute from the specified object. | |
*/ | |
public Object extractValueFromObjectForField(Object domainObject, DatabaseField field, AbstractSession session) throws DescriptorException { | |
// Allow for inheritance, the concrete descriptor must always be used. | |
ClassDescriptor descriptor = null;//this variable will be assigned in the final | |
if (this.descriptor.hasInheritance() && (domainObject.getClass() != this.descriptor.getJavaClass()) && ((descriptor = session.getDescriptor(domainObject)).getJavaClass() != this.descriptor.getJavaClass())) { | |
if(descriptor.isAggregateCollectionDescriptor()) { | |
descriptor = this.descriptor.getInheritancePolicy().getDescriptor(descriptor.getJavaClass()); | |
} | |
return descriptor.getObjectBuilder().extractValueFromObjectForField(domainObject, field, session); | |
} else { | |
DatabaseMapping mapping = getMappingForField(field); | |
if (mapping == null) { | |
throw DescriptorException.missingMappingForField(field, this.descriptor); | |
} | |
return mapping.valueFromObject(domainObject, field, session); | |
} | |
} | |
/** | |
* INTERNAL: | |
* An object has been serialized from the server to the client. | |
* Replace the transient attributes of the remote value holders | |
* with client-side objects. | |
*/ | |
public void fixObjectReferences(Object object, Map objectDescriptors, Map processedObjects, ObjectLevelReadQuery query, DistributedSession session) { | |
// PERF: Only process relationships. | |
if (!this.isSimple) { | |
List<DatabaseMapping> mappings = this.relationshipMappings; | |
for (int index = 0; index < mappings.size(); index++) { | |
mappings.get(index).fixObjectReferences(object, objectDescriptors, processedObjects, query, session); | |
} | |
} | |
} | |
/** | |
* Return the base ChangeRecord for the given DatabaseField. | |
* The object and all its relevant aggregates must exist. | |
* The returned ChangeRecord is | |
* either DirectToFieldChangeRecord or TransformationMappingChangeRecord, | |
* or null. | |
*/ | |
public ChangeRecord getBaseChangeRecordForField(ObjectChangeSet objectChangeSet, Object object, DatabaseField databaseField, AbstractSession session) { | |
DatabaseMapping mapping = getMappingForField(databaseField); | |
// Drill down through the mappings until we get the direct mapping to the databaseField. | |
while (mapping.isAggregateObjectMapping()) { | |
String attributeName = mapping.getAttributeName(); | |
Object aggregate = mapping.getAttributeValueFromObject(object); | |
ClassDescriptor referenceDescriptor = ((AggregateObjectMapping)mapping).getReferenceDescriptor(); | |
AggregateChangeRecord aggregateChangeRecord = (AggregateChangeRecord)objectChangeSet.getChangesForAttributeNamed(attributeName); | |
if (aggregateChangeRecord == null) { | |
aggregateChangeRecord = new AggregateChangeRecord(objectChangeSet); | |
aggregateChangeRecord.setAttribute(attributeName); | |
aggregateChangeRecord.setMapping(mapping); | |
objectChangeSet.addChange(aggregateChangeRecord); | |
} | |
ObjectChangeSet aggregateChangeSet = (ObjectChangeSet)aggregateChangeRecord.getChangedObject(); | |
if (aggregateChangeSet == null) { | |
aggregateChangeSet = referenceDescriptor.getObjectBuilder().createObjectChangeSet(aggregate, (UnitOfWorkChangeSet)objectChangeSet.getUOWChangeSet(), session); | |
aggregateChangeRecord.setChangedObject(aggregateChangeSet); | |
} | |
mapping = referenceDescriptor.getObjectBuilder().getMappingForField(databaseField); | |
objectChangeSet = aggregateChangeSet; | |
object = aggregate; | |
} | |
String attributeName = mapping.getAttributeName(); | |
if (mapping.isAbstractDirectMapping()) { | |
DirectToFieldChangeRecord changeRecord = (DirectToFieldChangeRecord)objectChangeSet.getChangesForAttributeNamed(attributeName); | |
if (changeRecord == null) { | |
changeRecord = new DirectToFieldChangeRecord(objectChangeSet); | |
changeRecord.setAttribute(attributeName); | |
changeRecord.setMapping(mapping); | |
objectChangeSet.addChange(changeRecord); | |
} | |
return changeRecord; | |
} else if (mapping.isTransformationMapping()) { | |
TransformationMappingChangeRecord changeRecord = (TransformationMappingChangeRecord)objectChangeSet.getChangesForAttributeNamed(attributeName); | |
if (changeRecord == null) { | |
changeRecord = new TransformationMappingChangeRecord(objectChangeSet); | |
changeRecord.setAttribute(attributeName); | |
changeRecord.setMapping(mapping); | |
objectChangeSet.addChange(changeRecord); | |
} | |
return changeRecord; | |
} else { | |
session.log(SessionLog.FINEST, SessionLog.QUERY, "field_for_unsupported_mapping_returned", databaseField, getDescriptor()); | |
return null; | |
} | |
} | |
/** | |
* Return the base mapping for the given DatabaseField. | |
*/ | |
public DatabaseMapping getBaseMappingForField(DatabaseField databaseField) { | |
DatabaseMapping mapping = getMappingForField(databaseField); | |
// Drill down through the mappings until we get the direct mapping to the databaseField. | |
while ((mapping != null) && mapping.isAggregateObjectMapping()) { | |
mapping = ((AggregateObjectMapping)mapping).getReferenceDescriptor().getObjectBuilder().getMappingForField(databaseField); | |
} | |
return mapping; | |
} | |
/** | |
* Return the base value that is mapped to for given field. | |
*/ | |
public Object getBaseValueForField(DatabaseField databaseField, Object domainObject) { | |
Object valueIntoObject = domainObject; | |
DatabaseMapping mapping = getMappingForField(databaseField); | |
// Drill down through the aggregate mappings to get to the direct to field mapping. | |
while (mapping.isAggregateObjectMapping()) { | |
valueIntoObject = mapping.getAttributeValueFromObject(valueIntoObject); | |
mapping = ((AggregateMapping)mapping).getReferenceDescriptor().getObjectBuilder().getMappingForField(databaseField); | |
} | |
// Bug 422610 | |
if (valueIntoObject == null) { | |
return null; | |
} | |
return mapping.getAttributeValueFromObject(valueIntoObject); | |
} | |
/** | |
* Return the descriptor | |
*/ | |
public ClassDescriptor getDescriptor() { | |
return descriptor; | |
} | |
/** | |
* INTERNAL: | |
* Return the classification for the field contained in the mapping. | |
* This is used to convert the row value to a consistent java value. | |
*/ | |
public Class getFieldClassification(DatabaseField fieldToClassify) throws DescriptorException { | |
DatabaseMapping mapping = getMappingForField(fieldToClassify); | |
if (mapping == null) { | |
// Means that the mapping is read-only or the classification is unknown, | |
// this is normally not an issue as the classification is only really used for primary keys | |
// and only when the database type can be different and not polymorphic than the object type. | |
return null; | |
} | |
return mapping.getFieldClassification(fieldToClassify); | |
} | |
/** | |
* Return the field used for the query key name. | |
*/ | |
public DatabaseField getFieldForQueryKeyName(String name) { | |
QueryKey key = this.descriptor.getQueryKeyNamed(name); | |
if (key == null) { | |
DatabaseMapping mapping = getMappingForAttributeName(name); | |
if (mapping == null) { | |
return null; | |
} | |
if (mapping.getFields().isEmpty()) { | |
return null; | |
} | |
return mapping.getFields().get(0); | |
} | |
if (key.isDirectQueryKey()) { | |
return ((DirectQueryKey)key).getField(); | |
} | |
return null; | |
} | |
/** | |
* Return the fields map. | |
* Used to maintain identity on the field objects. Ensure they get the correct index/type. | |
*/ | |
public Map<DatabaseField, DatabaseField> getFieldsMap() { | |
return fieldsMap; | |
} | |
/** | |
* Return the fields map. | |
* Used to maintain identity on the field objects. Ensure they get the correct index/type. | |
*/ | |
protected void setFieldsMap(Map fieldsMap) { | |
this.fieldsMap = fieldsMap; | |
} | |
/** | |
* PERF: | |
* Return all mappings that require cloning. | |
* This allows for simple directs to be avoided when using clone copying. | |
*/ | |
public List<DatabaseMapping> getCloningMappings() { | |
return cloningMappings; | |
} | |
/** | |
* PERF: | |
* Return if the descriptor has no complex mappings, all direct. | |
*/ | |
public boolean isSimple() { | |
return isSimple; | |
} | |
/** | |
* PERF: | |
* Return all relationship mappings. | |
*/ | |
public List<DatabaseMapping> getRelationshipMappings() { | |
return relationshipMappings; | |
} | |
/** | |
* PERF: | |
* Return all mappings that are eager loaded (but use indirection). | |
* This allows for eager mappings to still benefit from indirection for locking and change tracking. | |
*/ | |
public List<DatabaseMapping> getEagerMappings() { | |
return eagerMappings; | |
} | |
/** | |
* Answers the attributes which are always joined to the original query on reads. | |
*/ | |
public List<DatabaseMapping> getJoinedAttributes() { | |
return joinedAttributes; | |
} | |
/** | |
* Return the mappings that are always batch fetched. | |
*/ | |
public List<DatabaseMapping> getBatchFetchedAttributes() { | |
return this.batchFetchedAttributes; | |
} | |
/** | |
* PERF: | |
* Return the sequence mapping. | |
*/ | |
public AbstractDirectMapping getSequenceMapping() { | |
return sequenceMapping; | |
} | |
/** | |
* PERF: | |
* Set the sequence mapping. | |
*/ | |
public void setSequenceMapping(AbstractDirectMapping sequenceMapping) { | |
this.sequenceMapping = sequenceMapping; | |
} | |
/** | |
* Answers if any attributes are to be joined / returned in the same select | |
* statement. | |
*/ | |
public boolean hasJoinedAttributes() { | |
return (this.joinedAttributes != null); | |
} | |
/** | |
* Return is any mappings are always batch fetched. | |
*/ | |
public boolean hasBatchFetchedAttributes() { | |
return (this.batchFetchedAttributes != null); | |
} | |
/** | |
* Return is any mappings are always batch fetched using IN. | |
*/ | |
public boolean hasInBatchFetchedAttribute() { | |
return this.hasInBatchFetchedAttribute; | |
} | |
/** | |
* Set if any mappings are always batch fetched using IN. | |
*/ | |
public void setHasInBatchFetchedAttribute(boolean hasInBatchFetchedAttribute) { | |
this.hasInBatchFetchedAttribute = hasInBatchFetchedAttribute; | |
} | |
/** | |
* Return the mapping for the specified attribute name. | |
*/ | |
public DatabaseMapping getMappingForAttributeName(String name) { | |
return getMappingsByAttribute().get(name); | |
} | |
/** | |
* Return al the mapping for the specified field. | |
*/ | |
public DatabaseMapping getMappingForField(DatabaseField field) { | |
return getMappingsByField().get(field); | |
} | |
/** | |
* Return all the read-only mapping for the specified field. | |
*/ | |
public List<DatabaseMapping> getReadOnlyMappingsForField(DatabaseField field) { | |
return getReadOnlyMappingsByField().get(field); | |
} | |
/** | |
* Return all the mapping to attribute associations | |
*/ | |
protected Map<String, DatabaseMapping> getMappingsByAttribute() { | |
return mappingsByAttribute; | |
} | |
/** | |
* INTERNAL: | |
* Return all the mapping to field associations | |
*/ | |
public Map<DatabaseField, DatabaseMapping> getMappingsByField() { | |
return mappingsByField; | |
} | |
/** | |
* INTERNAL: | |
* Return all the read-only mapping to field associations | |
*/ | |
public Map<DatabaseField, List<DatabaseMapping>> getReadOnlyMappingsByField() { | |
return readOnlyMappingsByField; | |
} | |
/** | |
* Return the non primary key mappings. | |
*/ | |
protected List<DatabaseMapping> getNonPrimaryKeyMappings() { | |
return nonPrimaryKeyMappings; | |
} | |
/** | |
* Return the base value that is mapped to for given field. | |
*/ | |
public Object getParentObjectForField(DatabaseField databaseField, Object domainObject) { | |
Object valueIntoObject = domainObject; | |
DatabaseMapping mapping = getMappingForField(databaseField); | |
// Drill down through the aggregate mappings to get to the direct to field mapping. | |
while (mapping.isAggregateObjectMapping()) { | |
valueIntoObject = mapping.getAttributeValueFromObject(valueIntoObject); | |
mapping = ((AggregateMapping)mapping).getReferenceDescriptor().getObjectBuilder().getMappingForField(databaseField); | |
} | |
return valueIntoObject; | |
} | |
/** | |
* Return primary key classifications. | |
* These are used to ensure a consistent type for the pk values. | |
*/ | |
public List<Class> getPrimaryKeyClassifications() { | |
if (primaryKeyClassifications == null) { | |
List primaryKeyFields = this.descriptor.getPrimaryKeyFields(); | |
if(null == primaryKeyFields) { | |
return Collections.emptyList(); | |
} | |
List<Class> classifications = new ArrayList(primaryKeyFields.size()); | |
for (int index = 0; index < primaryKeyFields.size(); index++) { | |
if (getPrimaryKeyMappings().size() < (index + 1)) { // Check for failed initialization to avoid cascaded errors. | |
classifications.add(null); | |
} else { | |
DatabaseMapping mapping = getPrimaryKeyMappings().get(index); | |
DatabaseField field = (DatabaseField)primaryKeyFields.get(index); | |
if (mapping != null) { | |
classifications.add(Helper.getObjectClass(mapping.getFieldClassification(field))); | |
} else { | |
classifications.add(null); | |
} | |
} | |
} | |
primaryKeyClassifications = classifications; | |
} | |
return primaryKeyClassifications; | |
} | |
/** | |
* Return the primary key expression | |
*/ | |
public Expression getPrimaryKeyExpression() { | |
return primaryKeyExpression; | |
} | |
/** | |
* Return primary key mappings. | |
*/ | |
public List<DatabaseMapping> getPrimaryKeyMappings() { | |
return primaryKeyMappings; | |
} | |
/** | |
* INTERNAL: return a database field based on a query key name | |
*/ | |
public DatabaseField getTargetFieldForQueryKeyName(String queryKeyName) { | |
DatabaseMapping mapping = getMappingForAttributeName(queryKeyName); | |
if ((mapping != null) && mapping.isAbstractColumnMapping()) { | |
return ((AbstractColumnMapping)mapping).getField(); | |
} | |
//mapping is either null or not direct to field. | |
//check query keys | |
QueryKey queryKey = this.descriptor.getQueryKeyNamed(queryKeyName); | |
if ((queryKey != null) && queryKey.isDirectQueryKey()) { | |
return ((DirectQueryKey)queryKey).getField(); | |
} | |
//nothing found | |
return null; | |
} | |
/** | |
* Cache all the mappings by their attribute and fields. | |
*/ | |
public void initialize(AbstractSession session) throws DescriptorException { | |
getMappingsByField().clear(); | |
getReadOnlyMappingsByField().clear(); | |
getMappingsByAttribute().clear(); | |
getCloningMappings().clear(); | |
getEagerMappings().clear(); | |
getRelationshipMappings().clear(); | |
if (nonPrimaryKeyMappings == null) { | |
nonPrimaryKeyMappings = new ArrayList(10); | |
} | |
for (Enumeration mappings = this.descriptor.getMappings().elements(); | |
mappings.hasMoreElements();) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.nextElement(); | |
// Add attribute to mapping association | |
if (!mapping.isWriteOnly()) { | |
getMappingsByAttribute().put(mapping.getAttributeName(), mapping); | |
} | |
// Cache mappings that require cloning. | |
if (mapping.isCloningRequired()) { | |
getCloningMappings().add(mapping); | |
} | |
// Cache eager mappings. | |
if (mapping.isForeignReferenceMapping() && ((ForeignReferenceMapping)mapping).usesIndirection() && (!mapping.isLazy())) { | |
getEagerMappings().add(mapping); | |
} | |
if (mapping.getReferenceDescriptor() != null && mapping.isCollectionMapping()){ | |
// only process writable mappings on the defining class in the case of inheritance | |
if (getDescriptor() == mapping.getDescriptor()){ | |
((ContainerMapping)mapping).getContainerPolicy().processAdditionalWritableMapKeyFields(session); | |
} | |
} | |
// Cache relationship mappings. | |
if (!mapping.isAbstractColumnMapping()) { | |
getRelationshipMappings().add(mapping); | |
} | |
// Add field to mapping association | |
for (DatabaseField field : mapping.getFields()) { | |
if (mapping.isReadOnly()) { | |
List readOnlyMappings = getReadOnlyMappingsByField().get(field); | |
if (readOnlyMappings == null) { | |
readOnlyMappings = new ArrayList(); | |
getReadOnlyMappingsByField().put(field, readOnlyMappings); | |
} | |
readOnlyMappings.add(mapping); | |
} else { | |
if (mapping.isAggregateObjectMapping()) { | |
// For Embeddable class, we need to test read-only | |
// status of individual fields in the embeddable. | |
ObjectBuilder aggregateObjectBuilder = ((AggregateObjectMapping)mapping).getReferenceDescriptor().getObjectBuilder(); | |
// Look in the non-read-only fields mapping | |
DatabaseMapping aggregatedFieldMapping = aggregateObjectBuilder.getMappingForField(field); | |
if (aggregatedFieldMapping == null) { // mapping must be read-only | |
List readOnlyMappings = getReadOnlyMappingsByField().get(field); | |
if (readOnlyMappings == null) { | |
readOnlyMappings = new ArrayList(); | |
getReadOnlyMappingsByField().put(field, readOnlyMappings); | |
} | |
readOnlyMappings.add(mapping); | |
} else { | |
getMappingsByField().put(field, mapping); | |
} | |
} else { // Not an embeddable mapping | |
if (getMappingsByField().containsKey(field) || mapping.getDescriptor().getAdditionalWritableMapKeyFields().contains(field)) { | |
session.getIntegrityChecker().handleError(DescriptorException.multipleWriteMappingsForField(field.toString(), mapping)); | |
} else { | |
getMappingsByField().put(field, mapping); | |
} | |
} | |
} | |
} | |
} | |
this.isSimple = getRelationshipMappings().isEmpty(); | |
initializePrimaryKey(session); | |
initializeJoinedAttributes(); | |
initializeBatchFetchedAttributes(); | |
if (this.descriptor.usesSequenceNumbers()) { | |
DatabaseMapping sequenceMapping = getMappingForField(this.descriptor.getSequenceNumberField()); | |
if ((sequenceMapping != null) && sequenceMapping.isDirectToFieldMapping()) { | |
setSequenceMapping((AbstractDirectMapping)sequenceMapping); | |
} | |
} | |
if(this.descriptor.usesOptimisticLocking()) { | |
DatabaseField lockField = this.descriptor.getOptimisticLockingPolicy().getWriteLockField(); | |
if (lockField != null) { | |
DatabaseMapping lockMapping = getDescriptor().getObjectBuilder().getMappingForField(lockField); | |
if (lockMapping != null) { | |
this.lockAttribute = lockMapping.getAttributeName(); | |
} | |
} | |
} | |
} | |
public boolean isPrimaryKeyComponentInvalid(Object keyValue, int index) { | |
IdValidation idValidation; | |
if (index < 0) { | |
idValidation = this.descriptor.getIdValidation(); | |
} else { | |
idValidation = this.descriptor.getPrimaryKeyIdValidations().get(index); | |
} | |
if (idValidation == IdValidation.ZERO) { | |
return keyValue == null || Helper.isEquivalentToNull(keyValue); | |
} else if (idValidation == IdValidation.NULL) { | |
return keyValue == null; | |
} else if (idValidation == IdValidation.NEGATIVE) { | |
return keyValue == null || Helper.isNumberNegativeOrZero(keyValue); | |
} else { | |
// idValidation == IdValidation.NONE | |
return false; | |
} | |
} | |
public void recordPrivateOwnedRemovals(Object object, UnitOfWorkImpl uow, boolean initialPass) { | |
if (!this.descriptor.isDescriptorTypeAggregate()){ | |
if (!initialPass && uow.getDeletedObjects().containsKey(object)){ | |
return; | |
} | |
// do not delete private owned objects that do not exist | |
if (uow.doesObjectExist(object)){ | |
uow.getDeletedObjects().put(object, object); | |
} else { | |
uow.getCommitManager().markIgnoreCommit(object); | |
} | |
} | |
if (this.descriptor.hasMappingsPostCalculateChanges()){ | |
for (DatabaseMapping mapping : this.descriptor.getMappingsPostCalculateChanges()){ | |
mapping.recordPrivateOwnedRemovals(object, uow); | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* Post initializations after mappings are initialized. | |
*/ | |
public void postInitialize(AbstractSession session) throws DescriptorException { | |
// PERF: Cache if needs to unwrap to optimize unwrapping. | |
this.hasWrapperPolicy = this.descriptor.hasWrapperPolicy() || session.getProject().hasProxyIndirection(); | |
// PERF: Used by ObjectLevelReadQuery ResultSetAccessOptimization. | |
this.shouldKeepRow = false; | |
for (DatabaseField field : this.descriptor.getFields()) { | |
if (field.keepInRow()) { | |
this.shouldKeepRow = true; | |
break; | |
} | |
} | |
// PERF: is there an cache index field that's would not be selected by SOP query. Ignored unless descriptor uses SOP and CachePolicy has cache indexes. | |
if (this.descriptor.hasSerializedObjectPolicy() && this.descriptor.getCachePolicy().hasCacheIndexes()) { | |
for (List<DatabaseField> indexFields : this.descriptor.getCachePolicy().getCacheIndexes().keySet()) { | |
if (!this.descriptor.getSerializedObjectPolicy().getSelectionFields().containsAll(indexFields)) { | |
this.hasCacheIndexesInSopObject = true; | |
break; | |
} | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* Iterates through all one to one mappings and checks if any of them use joining. | |
* <p> | |
* By caching the result query execution in the case where there are no joined | |
* attributes can be improved. | |
*/ | |
public void initializeJoinedAttributes() { | |
// For concurrency don't worry about doing this work twice, just make sure | |
// if it happens don't add the same joined attributes twice. | |
List<DatabaseMapping> joinedAttributes = null; | |
List mappings = this.descriptor.getMappings(); | |
for (int i = 0; i < mappings.size(); i++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(i); | |
if (mapping.isForeignReferenceMapping() && ((ForeignReferenceMapping)mapping).isJoinFetched()) { | |
if (joinedAttributes == null) { | |
joinedAttributes = new ArrayList(); | |
} | |
joinedAttributes.add(mapping); | |
} | |
} | |
this.joinedAttributes = joinedAttributes; | |
} | |
/** | |
* INTERNAL: | |
* Iterates through all one to one mappings and checks if any of them use batch fetching. | |
* <p> | |
* By caching the result query execution in the case where there are no batch fetched | |
* attributes can be improved. | |
*/ | |
public void initializeBatchFetchedAttributes() { | |
List<DatabaseMapping> batchedAttributes = null; | |
for (DatabaseMapping mapping : this.descriptor.getMappings()) { | |
if (mapping.isForeignReferenceMapping() && ((ForeignReferenceMapping)mapping).shouldUseBatchReading()) { | |
if (batchedAttributes == null) { | |
batchedAttributes = new ArrayList(); | |
} | |
batchedAttributes.add(mapping); | |
if (((ForeignReferenceMapping)mapping).getBatchFetchType() == BatchFetchType.IN) { | |
this.hasInBatchFetchedAttribute = true; | |
} | |
} else if (mapping.isAggregateObjectMapping()) { | |
if (mapping.getReferenceDescriptor().getObjectBuilder().hasInBatchFetchedAttribute()) { | |
this.hasInBatchFetchedAttribute = true; | |
} | |
} | |
} | |
this.batchFetchedAttributes = batchedAttributes; | |
if (this.hasInBatchFetchedAttribute && this.descriptor.hasInheritance()) { | |
ClassDescriptor parent = this.descriptor.getInheritancePolicy().getParentDescriptor(); | |
while (parent != null) { | |
parent.getObjectBuilder().setHasInBatchFetchedAttribute(true); | |
parent = parent.getInheritancePolicy().getParentDescriptor(); | |
} | |
} | |
} | |
/** | |
* Initialize a cache key. Called by buildObject and now also by | |
* buildWorkingCopyCloneFromRow. | |
*/ | |
protected void copyQueryInfoToCacheKey(CacheKey cacheKey, ObjectBuildingQuery query, AbstractRecord databaseRow, AbstractSession session, ClassDescriptor concreteDescriptor) { | |
//CR #4365 - used to prevent infinite recursion on refresh object cascade all | |
cacheKey.setLastUpdatedQueryId(query.getQueryId()); | |
if (concreteDescriptor.usesOptimisticLocking()) { | |
OptimisticLockingPolicy policy = concreteDescriptor.getOptimisticLockingPolicy(); | |
Object cacheValue = policy.getValueToPutInCache(databaseRow, session); | |
//register the object into the IM and set the write lock object | |
cacheKey.setWriteLockValue(cacheValue); | |
} | |
cacheKey.setReadTime(query.getExecutionTime()); | |
} | |
/** | |
* Cache primary key and non primary key mappings. | |
*/ | |
public void initializePrimaryKey(AbstractSession session) throws DescriptorException { | |
List primaryKeyFields = this.descriptor.getPrimaryKeyFields(); | |
if ((null == primaryKeyFields || primaryKeyFields.isEmpty()) && getDescriptor().isAggregateCollectionDescriptor()) { | |
// populate primaryKeys with all mapped fields found in the main table. | |
DatabaseTable defaultTable = getDescriptor().getDefaultTable(); | |
Iterator<DatabaseField> it = getDescriptor().getFields().iterator(); | |
while(it.hasNext()) { | |
DatabaseField field = it.next(); | |
if(field.getTable().equals(defaultTable) && getMappingsByField().containsKey(field)) { | |
primaryKeyFields.add(field); | |
} | |
} | |
List<DatabaseField> additionalFields = this.descriptor.getAdditionalAggregateCollectionKeyFields(); | |
for(int i=0; i < additionalFields.size(); i++) { | |
DatabaseField additionalField = additionalFields.get(i); | |
if(!primaryKeyFields.contains(additionalField)) { | |
primaryKeyFields.add(additionalField); | |
} | |
} | |
} | |
createPrimaryKeyExpression(session); | |
if(null != primaryKeyMappings) { | |
primaryKeyMappings.clear(); | |
} | |
// This must be before because the secondary table primary key fields are registered after | |
//but no point doing it if the nonPrimaryKeyMappings collection is null | |
if (nonPrimaryKeyMappings != null) { | |
nonPrimaryKeyMappings.clear(); | |
for (Iterator fields = getMappingsByField().keySet().iterator(); fields.hasNext();) { | |
DatabaseField field = (DatabaseField)fields.next(); | |
if (null ==primaryKeyFields || !primaryKeyFields.contains(field)) { | |
DatabaseMapping mapping = getMappingForField(field); | |
if (!getNonPrimaryKeyMappings().contains(mapping)) { | |
getNonPrimaryKeyMappings().add(mapping); | |
} | |
} | |
} | |
} | |
if(null != primaryKeyFields) { | |
for (int index = 0; index < primaryKeyFields.size(); index++) { | |
DatabaseField primaryKeyField = (DatabaseField)primaryKeyFields.get(index); | |
DatabaseMapping mapping = getMappingForField(primaryKeyField); | |
if (mapping == null) { | |
if(this.descriptor.isDescriptorTypeAggregate()) { | |
this.mayHaveNullInPrimaryKey = true; | |
} else { | |
throw DescriptorException.noMappingForPrimaryKey(primaryKeyField, this.descriptor); | |
} | |
} | |
getPrimaryKeyMappings().add(mapping); | |
if (mapping != null) { | |
mapping.setIsPrimaryKeyMapping(true); | |
} | |
// Use the same mapping to map the additional table primary key fields. | |
// This is required if someone tries to map to one of these fields. | |
if (this.descriptor.hasMultipleTables() && (mapping != null)) { | |
for (Map keyMapping : this.descriptor.getAdditionalTablePrimaryKeyFields().values()) { | |
DatabaseField secondaryField = (DatabaseField) keyMapping.get(primaryKeyField); | |
// This can be null in the custom multiple join case | |
if (secondaryField != null) { | |
getMappingsByField().put(secondaryField, mapping); | |
if (mapping.isAggregateObjectMapping()) { | |
// GF#1153,1391 | |
// If AggregateObjectMapping contain primary keys and the descriptor has multiple tables | |
// AggregateObjectMapping should know the the primary key join columns (secondaryField here) | |
// to handle some cases properly | |
((AggregateObjectMapping) mapping).addPrimaryKeyJoinField(primaryKeyField, secondaryField); | |
} | |
} | |
} | |
} | |
} | |
} | |
// PERF: compute if primary key is mapped through direct mappings, | |
// to allow fast extraction. | |
boolean hasSimplePrimaryKey = true; | |
if(null != primaryKeyMappings) { | |
for (int index = 0; index < getPrimaryKeyMappings().size(); index++) { | |
DatabaseMapping mapping = getPrimaryKeyMappings().get(index); | |
// Primary key mapping may be null for aggregate collection. | |
if ((mapping == null) || (!mapping.isAbstractColumnMapping())) { | |
hasSimplePrimaryKey = false; | |
break; | |
} | |
} | |
} | |
this.descriptor.setHasSimplePrimaryKey(hasSimplePrimaryKey); | |
// Set id validation, zero is allowed for composite primary keys. | |
boolean wasIdValidationSet = true; | |
if (this.descriptor.getIdValidation() == null) { | |
wasIdValidationSet = false; | |
List<DatabaseField> descriptorPrimaryKeyFields = this.descriptor.getPrimaryKeyFields(); | |
if (descriptorPrimaryKeyFields != null && descriptorPrimaryKeyFields.size() > 1) { | |
this.descriptor.setIdValidation(IdValidation.NULL); | |
} else { | |
this.descriptor.setIdValidation(IdValidation.ZERO); | |
} | |
} | |
// Initialize id validation per field, default sequence to allowing zero. | |
// This defaults to allowing zero for the other fields. | |
if (this.descriptor.getPrimaryKeyFields() != null && this.descriptor.getPrimaryKeyIdValidations() == null) { | |
this.descriptor.setPrimaryKeyIdValidations(new ArrayList(this.descriptor.getPrimaryKeyFields().size())); | |
for (DatabaseField field : this.descriptor.getPrimaryKeyFields()) { | |
if (!wasIdValidationSet && this.descriptor.usesSequenceNumbers() && field.equals(this.descriptor.getSequenceNumberField())) { | |
this.descriptor.getPrimaryKeyIdValidations().add(IdValidation.ZERO); | |
} else { | |
this.descriptor.getPrimaryKeyIdValidations().add(this.descriptor.getIdValidation()); | |
} | |
} | |
} | |
} | |
/** | |
* Returns the clone of the specified object. This is called only from unit of work. | |
* This only instantiates the clone instance, it does not clone the attributes, | |
* this allows the stub of the clone to be registered before cloning its parts. | |
*/ | |
public Object instantiateClone(Object domainObject, AbstractSession session) { | |
Object clone = this.descriptor.getCopyPolicy().buildClone(domainObject, session); | |
// Clear change tracker. | |
if (clone instanceof ChangeTracker) { | |
((ChangeTracker)clone)._persistence_setPropertyChangeListener(null); | |
} | |
if(clone instanceof FetchGroupTracker) { | |
((FetchGroupTracker)clone)._persistence_setFetchGroup(null); | |
((FetchGroupTracker)clone)._persistence_setSession(null); | |
} | |
clearPrimaryKey(clone); | |
return clone; | |
} | |
/** | |
* Returns the clone of the specified object. This is called only from unit of work. | |
* The domainObject sent as parameter is always a copy from the parent of unit of work. | |
* bug 2612602 make a call to build a working clone. This will in turn call the copy policy | |
* to make a working clone. This allows for lighter and heavier clones to | |
* be created based on their use. | |
* this allows the stub of the clone to be registered before cloning its parts. | |
*/ | |
public Object instantiateWorkingCopyClone(Object domainObject, AbstractSession session) { | |
return this.descriptor.getCopyPolicy().buildWorkingCopyClone(domainObject, session); | |
} | |
/** | |
* It is now possible to build working copy clones directly from rows. | |
* <p>An intermediary original is no longer needed. | |
* <p>This has ramifications to the copy policy and cmp, for clones are | |
* no longer built via cloning. | |
* <p>Instead the copy policy must in some cases not copy at all. | |
* this allows the stub of the clone to be registered before cloning its parts. | |
*/ | |
public Object instantiateWorkingCopyCloneFromRow(AbstractRecord row, ObjectBuildingQuery query, Object primaryKey, UnitOfWorkImpl unitOfWork) { | |
return this.descriptor.getCopyPolicy().buildWorkingCopyCloneFromRow(row, query, primaryKey, unitOfWork); | |
} | |
public boolean isPrimaryKeyMapping(DatabaseMapping mapping) { | |
return getPrimaryKeyMappings().contains(mapping); | |
} | |
/** | |
* INTERNAL: | |
* Perform the iteration operation on the objects attributes through the mappings. | |
*/ | |
public void iterate(DescriptorIterator iterator) { | |
List<DatabaseMapping> mappings; | |
// Only iterate on relationships if required. | |
if (iterator.shouldIterateOnPrimitives()) { | |
mappings = this.descriptor.getMappings(); | |
} else { | |
// PERF: Only process relationships. | |
if (this.isSimple) { | |
return; | |
} | |
mappings = this.relationshipMappings; | |
} | |
int mappingsSize = mappings.size(); | |
for (int index = 0; index < mappingsSize; index++) { | |
mappings.get(index).iterate(iterator); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Merge changes between the objects, this merge algorithm is dependent on the merge manager. | |
*/ | |
public void mergeChangesIntoObject(Object target, ObjectChangeSet changeSet, Object source, MergeManager mergeManager, AbstractSession targetSession) { | |
mergeChangesIntoObject(target, changeSet, source, mergeManager, targetSession, false, false); | |
} | |
/** | |
* INTERNAL: | |
* Merge changes between the objects, this merge algorithm is dependent on the merge manager. | |
*/ | |
public void mergeChangesIntoObject(Object target, ObjectChangeSet changeSet, Object source, MergeManager mergeManager, AbstractSession targetSession, boolean isTargetCloneOfOriginal, boolean shouldMergeFetchGroup) { | |
// PERF: Just merge the object for new objects, as the change set is not populated. | |
if ((source != null) && changeSet.isNew() && (!this.descriptor.shouldUseFullChangeSetsForNewObjects())) { | |
mergeIntoObject(target, changeSet, true, source, mergeManager, targetSession, false, isTargetCloneOfOriginal, shouldMergeFetchGroup); | |
} else { | |
List changes = changeSet.getChanges(); | |
int size = changes.size(); | |
for (int index = 0; index < size; index++) { | |
ChangeRecord record = (ChangeRecord)changes.get(index); | |
//cr 4236, use ObjectBuilder getMappingForAttributeName not the Descriptor one because the | |
// ObjectBuilder method is much more efficient. | |
DatabaseMapping mapping = getMappingForAttributeName(record.getAttribute()); | |
mapping.mergeChangesIntoObject(target, record, source, mergeManager, targetSession); | |
} | |
// PERF: Avoid events if no listeners. | |
// Event is already raised in mergeIntoObject, avoid calling twice. | |
if (this.descriptor.getEventManager().hasAnyEventListeners()) { | |
DescriptorEvent event = new DescriptorEvent(target); | |
event.setSession(mergeManager.getSession()); | |
event.setOriginalObject(source); | |
event.setChangeSet(changeSet); | |
event.setEventCode(DescriptorEventManager.PostMergeEvent); | |
this.descriptor.getEventManager().executeEvent(event); | |
} | |
} | |
this.descriptor.getCachePolicy().indexObjectInCache(changeSet, target, this.descriptor, targetSession); | |
} | |
/** | |
* INTERNAL: | |
* Merge the contents of one object into another, this merge algorithm is dependent on the merge manager. | |
* This merge also prevents the extra step of calculating the changes when it is not required. | |
*/ | |
public void mergeIntoObject(Object target, boolean isUnInitialized, Object source, MergeManager mergeManager, AbstractSession targetSession) { | |
mergeIntoObject(target, null, isUnInitialized, source, mergeManager, targetSession, false, false, false); | |
} | |
/** | |
* INTERNAL: | |
* Merge the contents of one object into another, this merge algorithm is dependent on the merge manager. | |
* This merge also prevents the extra step of calculating the changes when it is not required. | |
* If 'cascadeOnly' is true, only foreign reference mappings are merged. | |
* If 'isTargetCloneOfOriginal' then the target was create through a shallow clone of the source, so merge basics is not required. | |
*/ | |
public void mergeIntoObject(Object target, ObjectChangeSet changeSet, boolean isUnInitialized, Object source, MergeManager mergeManager, AbstractSession targetSession, boolean cascadeOnly, boolean isTargetCloneOfOriginal, boolean shouldMergeFetchGroup) { | |
// cascadeOnly is introduced to optimize merge | |
// for GF#1139 Cascade merge operations to relationship mappings even if already registered | |
FetchGroup sourceFetchGroup = null; | |
FetchGroup targetFetchGroup = null; | |
if(this.descriptor.hasFetchGroupManager()) { | |
sourceFetchGroup = this.descriptor.getFetchGroupManager().getObjectFetchGroup(source); | |
targetFetchGroup = this.descriptor.getFetchGroupManager().getObjectFetchGroup(target); | |
if(targetFetchGroup != null) { | |
if(!targetFetchGroup.isSupersetOf(sourceFetchGroup)) { | |
targetFetchGroup.onUnfetchedAttribute((FetchGroupTracker)target, null); | |
} | |
} else if (shouldMergeFetchGroup && sourceFetchGroup != null){ | |
this.descriptor.getFetchGroupManager().setObjectFetchGroup(target, sourceFetchGroup, targetSession); | |
} | |
} | |
// PERF: Avoid synchronized enumerator as is concurrency bottleneck. | |
List<DatabaseMapping> mappings = this.descriptor.getMappings(); | |
int size = mappings.size(); | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = mappings.get(index); | |
if (((!cascadeOnly && !isTargetCloneOfOriginal) | |
|| (cascadeOnly && mapping.isForeignReferenceMapping()) | |
|| (isTargetCloneOfOriginal && mapping.isCloningRequired())) | |
&& (sourceFetchGroup == null || sourceFetchGroup.containsAttributeInternal(mapping.getAttributeName()))) { | |
mapping.mergeIntoObject(target, isUnInitialized, source, mergeManager, targetSession); | |
} | |
} | |
// PERF: Avoid events if no listeners. | |
if (this.descriptor.getEventManager().hasAnyEventListeners()) { | |
DescriptorEvent event = new DescriptorEvent(target); | |
event.setSession(mergeManager.getSession()); | |
event.setOriginalObject(source); | |
event.setChangeSet(changeSet); | |
event.setEventCode(DescriptorEventManager.PostMergeEvent); | |
this.descriptor.getEventManager().executeEvent(event); | |
} | |
} | |
/** | |
* Clones the attributes of the specified object. This is called only from unit of work. | |
* The domainObject sent as parameter is always a copy from the parent of unit of work. | |
*/ | |
public void populateAttributesForClone(Object original, CacheKey cacheKey, Object clone, Integer refreshCascade, AbstractSession cloningSession) { | |
List mappings = getCloningMappings(); | |
int size = mappings.size(); | |
if (this.descriptor.hasFetchGroupManager() && this.descriptor.getFetchGroupManager().isPartialObject(original)) { | |
FetchGroupManager fetchGroupManager = this.descriptor.getFetchGroupManager(); | |
for (int index = 0; index < size; index++) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); | |
if (fetchGroupManager.isAttributeFetched(original, mapping.getAttributeName())) { | |
mapping.buildClone(original, cacheKey, clone, refreshCascade, cloningSession); | |
} | |
} | |
} else { | |
for (int index = 0; index < size; index++) { | |
((DatabaseMapping)mappings.get(index)).buildClone(original, cacheKey, clone, refreshCascade, cloningSession); | |
} | |
} | |
// PERF: Avoid events if no listeners. | |
if (this.descriptor.getEventManager().hasAnyEventListeners()) { | |
DescriptorEvent event = new DescriptorEvent(clone); | |
event.setSession(cloningSession); | |
event.setOriginalObject(original); | |
event.setDescriptor(descriptor); | |
event.setEventCode(DescriptorEventManager.PostCloneEvent); | |
cloningSession.deferEvent(event); | |
} | |
} | |
protected void loadBatchReadAttributes(ClassDescriptor concreteDescriptor, Object sourceObject, CacheKey cacheKey, AbstractRecord databaseRow, ObjectBuildingQuery query, JoinedAttributeManager joinManager, boolean isTargetProtected){ | |
boolean useOnlyMappingsExcludedFromSOP = false; | |
if (concreteDescriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy()) { | |
// if true then sopObject has not been deserialized, that means sourceObject has been cached. | |
useOnlyMappingsExcludedFromSOP = databaseRow.get(concreteDescriptor.getSerializedObjectPolicy().getField()) != null; | |
} | |
boolean isUntriggeredResultSetRecord = databaseRow instanceof ResultSetRecord && ((ResultSetRecord)databaseRow).hasResultSet(); | |
List batchExpressions = ((ReadAllQuery)query).getBatchReadAttributeExpressions(); | |
int size = batchExpressions.size(); | |
for (int index = 0; index < size; index++) { | |
QueryKeyExpression queryKeyExpression = (QueryKeyExpression)batchExpressions.get(index); | |
// Only worry about immediate attributes. | |
if (queryKeyExpression.getBaseExpression().isExpressionBuilder()) { | |
DatabaseMapping mapping = getMappingForAttributeName(queryKeyExpression.getName()); | |
if (mapping == null) { | |
throw ValidationException.missingMappingForAttribute(concreteDescriptor, queryKeyExpression.getName(), this.toString()); | |
} else { | |
if (!useOnlyMappingsExcludedFromSOP || mapping.isOutSopObject()) { | |
// Bug 4230655 - do not replace instantiated valueholders. | |
Object attributeValue = mapping.getAttributeValueFromObject(sourceObject); | |
if ((attributeValue != null) && mapping.isForeignReferenceMapping() && ((ForeignReferenceMapping)mapping).usesIndirection() && (!((ForeignReferenceMapping)mapping).getIndirectionPolicy().objectIsInstantiated(attributeValue))) { | |
if (isUntriggeredResultSetRecord && mapping.isObjectReferenceMapping() && ((ObjectReferenceMapping)mapping).isForeignKeyRelationship() && !mapping.isPrimaryKeyMapping()) { | |
// ResultSetRecord hasn't been triggered (still has ResultSet), but values for its primary key field(s) were already extracted from ResultSet, | |
// still need to extract values from ResultSet for foreign key fields. | |
for (DatabaseField field : mapping.getFields()) { | |
// extract the values from ResultSet into the row | |
databaseRow.get(field); | |
} | |
} | |
AbstractSession session = query.getExecutionSession(); | |
mapping.readFromRowIntoObject(databaseRow, joinManager, sourceObject, cacheKey, query, query.getExecutionSession(),isTargetProtected); | |
session.getIdentityMapAccessorInstance().getIdentityMap(concreteDescriptor).lazyRelationshipLoaded(sourceObject, (ValueHolderInterface) ((ForeignReferenceMapping)mapping).getIndirectionPolicy().getOriginalValueHolder(attributeValue, session), (ForeignReferenceMapping)mapping); | |
} | |
} | |
} | |
} | |
} | |
} | |
protected void loadJoinedAttributes(ClassDescriptor concreteDescriptor, Object sourceObject, CacheKey cacheKey, AbstractRecord databaseRow, JoinedAttributeManager joinManager, ObjectBuildingQuery query, boolean isTargetProtected){ | |
boolean useOnlyMappingsExcludedFromSOP = false; | |
if (concreteDescriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy()) { | |
// sopObject has not been deserialized, sourceObject must be cached | |
useOnlyMappingsExcludedFromSOP = databaseRow.get(concreteDescriptor.getSerializedObjectPolicy().getField()) != null; | |
} | |
Boolean isUntriggeredResultSetRecord = null; | |
List joinExpressions = joinManager.getJoinedAttributeExpressions(); | |
int size = joinExpressions.size(); | |
for (int index = 0; index < size; index++) { | |
QueryKeyExpression queryKeyExpression = (QueryKeyExpression)joinExpressions.get(index); | |
QueryKeyExpression baseExpression = (QueryKeyExpression)joinManager.getJoinedAttributes().get(index); | |
DatabaseMapping mapping = joinManager.getJoinedAttributeMappings().get(index); | |
// Only worry about immediate (excluding aggregates) foreign reference mapping attributes. | |
if (queryKeyExpression == baseExpression) { | |
if (mapping == null) { | |
throw ValidationException.missingMappingForAttribute(concreteDescriptor, queryKeyExpression.getName(), toString()); | |
} else { | |
if (!useOnlyMappingsExcludedFromSOP || mapping.isOutSopObject()) { | |
//get the intermediate objects between this expression node and the base builder | |
Object intermediateValue = joinManager.getValueFromObjectForExpression(query.getExecutionSession(), sourceObject, (ObjectExpression)baseExpression.getBaseExpression()); | |
// Bug 4230655 - do not replace instantiated valueholders. | |
Object attributeValue = mapping.getAttributeValueFromObject(intermediateValue); | |
if ((attributeValue != null) && mapping.isForeignReferenceMapping() && ((ForeignReferenceMapping)mapping).usesIndirection() && (!((ForeignReferenceMapping)mapping).getIndirectionPolicy().objectIsInstantiated(attributeValue))) { | |
if (mapping.isObjectReferenceMapping() && ((ObjectReferenceMapping)mapping).isForeignKeyRelationship() && !mapping.isPrimaryKeyMapping()) { | |
if (isUntriggeredResultSetRecord == null) { | |
isUntriggeredResultSetRecord = Boolean.valueOf(databaseRow instanceof ResultSetRecord && ((ResultSetRecord)databaseRow).hasResultSet()); | |
} | |
if (isUntriggeredResultSetRecord) { | |
for (DatabaseField field : mapping.getFields()) { | |
// extract the values from ResultSet into the row | |
databaseRow.get(field); | |
} | |
} | |
} | |
AbstractSession session = query.getExecutionSession(); | |
mapping.readFromRowIntoObject(databaseRow, joinManager, intermediateValue, cacheKey, query, query.getExecutionSession(), isTargetProtected); | |
session.getIdentityMapAccessorInstance().getIdentityMap(concreteDescriptor).lazyRelationshipLoaded(intermediateValue, (ValueHolderInterface) ((ForeignReferenceMapping)mapping).getIndirectionPolicy().getOriginalValueHolder(attributeValue, session), (ForeignReferenceMapping)mapping); | |
} | |
} | |
} | |
} | |
} | |
} | |
/** | |
* This method is called when a cached Entity needs to be refreshed | |
*/ | |
protected boolean refreshObjectIfRequired(ClassDescriptor concreteDescriptor, CacheKey cacheKey, Object domainObject, ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, AbstractSession session, boolean targetIsProtected){ | |
boolean cacheHit = true; | |
FetchGroup fetchGroup = query.getExecutionFetchGroup(concreteDescriptor); | |
FetchGroupManager fetchGroupManager = concreteDescriptor.getFetchGroupManager(); | |
//cached object might be partially fetched, only refresh the fetch group attributes of the query if | |
//the cached partial object is not invalidated and does not contain all data for the fetch group. | |
if (fetchGroupManager != null && fetchGroupManager.isPartialObject(domainObject)) { | |
cacheHit = false; | |
//only ObjectLevelReadQuery and above support partial objects | |
revertFetchGroupData(domainObject, concreteDescriptor, cacheKey, (query), joinManager, databaseRow, session, targetIsProtected); | |
} else { | |
boolean refreshRequired = true; | |
if (concreteDescriptor.usesOptimisticLocking()) { | |
OptimisticLockingPolicy policy = concreteDescriptor.getOptimisticLockingPolicy(); | |
Object cacheValue = policy.getValueToPutInCache(databaseRow, session); | |
if (concreteDescriptor.getCachePolicy().shouldOnlyRefreshCacheIfNewerVersion()) { | |
if (cacheValue == null) { | |
refreshRequired = policy.isNewerVersion(databaseRow, domainObject, cacheKey.getKey(), session); | |
} else { | |
// avoid extracting lock value from the row for the second time, that would unnecessary trigger ResultSetRecord | |
refreshRequired = policy.isNewerVersion(cacheValue, domainObject, cacheKey.getKey(), session); | |
} | |
if (!refreshRequired) { | |
cacheKey.setReadTime(query.getExecutionTime()); | |
} | |
} | |
if (refreshRequired) { | |
// Update the write lock value. | |
cacheKey.setWriteLockValue(cacheValue); | |
} | |
} | |
if (refreshRequired) { | |
cacheHit = false; | |
// CR #4365 - used to prevent infinite recursion on refresh object cascade all. | |
cacheKey.setLastUpdatedQueryId(query.getQueryId()); | |
// Bug 276362 - set the CacheKey's read time (re-validating the CacheKey) before buildAttributesIntoObject is called | |
cacheKey.setReadTime(query.getExecutionTime()); | |
concreteDescriptor.getObjectBuilder().buildAttributesIntoObject(domainObject, cacheKey, databaseRow, query, joinManager, fetchGroup, true, session); | |
} | |
} | |
return cacheHit; | |
} | |
/** | |
* Rehash any maps based on fields. | |
* This is used to clone descriptors for aggregates, which hammer field names, | |
* it is probably better not to hammer the field name and this should be refactored. | |
*/ | |
public void rehashFieldDependancies(AbstractSession session) { | |
setMappingsByField(Helper.rehashMap(getMappingsByField())); | |
setReadOnlyMappingsByField(Helper.rehashMap(getReadOnlyMappingsByField())); | |
setFieldsMap(Helper.rehashMap(getFieldsMap())); | |
setPrimaryKeyMappings(new ArrayList(2)); | |
setNonPrimaryKeyMappings(new ArrayList(2)); | |
initializePrimaryKey(session); | |
} | |
/** | |
* Set the descriptor. | |
*/ | |
public void setDescriptor(ClassDescriptor aDescriptor) { | |
descriptor = aDescriptor; | |
} | |
/** | |
* All the mappings and their respective attribute associations are cached for performance improvement. | |
*/ | |
protected void setMappingsByAttribute(Map<String, DatabaseMapping> theAttributeMappings) { | |
mappingsByAttribute = theAttributeMappings; | |
} | |
/** | |
* INTERNAL: | |
* All the mappings and their respective field associations are cached for performance improvement. | |
*/ | |
public void setMappingsByField(Map<DatabaseField, DatabaseMapping> theFieldMappings) { | |
mappingsByField = theFieldMappings; | |
} | |
/** | |
* INTERNAL: | |
* All the read-only mappings and their respective field associations are cached for performance improvement. | |
*/ | |
public void setReadOnlyMappingsByField(Map<DatabaseField, List<DatabaseMapping>> theReadOnlyFieldMappings) { | |
readOnlyMappingsByField = theReadOnlyFieldMappings; | |
} | |
/** | |
* The non primary key mappings are cached to improve performance. | |
*/ | |
protected void setNonPrimaryKeyMappings(List<DatabaseMapping> theNonPrimaryKeyMappings) { | |
nonPrimaryKeyMappings = theNonPrimaryKeyMappings; | |
} | |
/** | |
* INTERNAL: | |
* Set primary key classifications. | |
* These are used to ensure a consistent type for the pk values. | |
*/ | |
public void setPrimaryKeyClassifications(List<Class> primaryKeyClassifications) { | |
this.primaryKeyClassifications = primaryKeyClassifications; | |
} | |
/** | |
* The primary key expression is cached to improve performance. | |
*/ | |
public void setPrimaryKeyExpression(Expression criteria) { | |
primaryKeyExpression = criteria; | |
} | |
/** | |
* The primary key mappings are cached to improve performance. | |
*/ | |
protected void setPrimaryKeyMappings(List<DatabaseMapping> thePrimaryKeyMappings) { | |
primaryKeyMappings = thePrimaryKeyMappings; | |
} | |
public String toString() { | |
return Helper.getShortClassName(getClass()) + "(" + this.descriptor.toString() + ")"; | |
} | |
/** | |
* Unwrap the object if required. | |
* This is used for the wrapper policy support and EJB. | |
*/ | |
public Object unwrapObject(Object proxy, AbstractSession session) { | |
if (!this.hasWrapperPolicy) { | |
return proxy; | |
} | |
if (proxy == null) { | |
return null; | |
} | |
// PERF: Using direct variable access. | |
// Check if already unwrapped. | |
if ((!this.descriptor.hasWrapperPolicy()) || (this.descriptor.getJavaClass() == proxy.getClass()) || (!this.descriptor.getWrapperPolicy().isWrapped(proxy))) { | |
if (session.getProject().hasProxyIndirection()) { | |
//Bug#3947714 Check and trigger the proxy here | |
return ProxyIndirectionPolicy.getValueFromProxy(proxy); | |
} | |
return proxy; | |
} | |
// Allow for inheritance, the concrete wrapper must always be used. | |
if (this.descriptor.hasInheritance() && (this.descriptor.getInheritancePolicy().hasChildren())) { | |
ClassDescriptor descriptor = session.getDescriptor(proxy); | |
if (descriptor != this.descriptor) { | |
return descriptor.getObjectBuilder().unwrapObject(proxy, session); | |
} | |
} | |
return this.descriptor.getWrapperPolicy().unwrapObject(proxy, session); | |
} | |
/** | |
* INTERNAL: | |
* Used to updated any attributes that may be cached on a woven entity | |
*/ | |
public void updateCachedAttributes(PersistenceEntity persistenceEntity, CacheKey cacheKey, Object primaryKey){ | |
persistenceEntity._persistence_setCacheKey(cacheKey); | |
persistenceEntity._persistence_setId(primaryKey); | |
} | |
/** | |
* Validates the object builder. This is done once the object builder initialized and descriptor | |
* fires this validation. | |
*/ | |
public void validate(AbstractSession session) throws DescriptorException { | |
if (this.descriptor.usesSequenceNumbers()) { | |
if (getMappingForField(this.descriptor.getSequenceNumberField()) == null) { | |
throw DescriptorException.mappingForSequenceNumberField(this.descriptor); | |
} | |
} | |
} | |
/** | |
* Verify that an object has been deleted from the database. | |
* An object can span multiple tables. A query is performed on each of | |
* these tables using the primary key values of the object as the selection | |
* criteria. If the query returns a result then the object has not been | |
* deleted from the database. If no result is returned then each of the | |
* mappings is asked to verify that the object has been deleted. If all mappings | |
* answer true then the result is true. | |
*/ | |
public boolean verifyDelete(Object object, AbstractSession session) { | |
AbstractRecord translationRow = buildRowForTranslation(object, session); | |
// If a call is used generated SQL cannot be executed, the call must be used. | |
if ((this.descriptor.getQueryManager().getReadObjectQuery() != null) && this.descriptor.getQueryManager().getReadObjectQuery().isCallQuery()) { | |
Object result = session.readObject(object); | |
if (result != null) { | |
return false; | |
} | |
} else { | |
for (Enumeration tables = this.descriptor.getTables().elements(); | |
tables.hasMoreElements();) { | |
DatabaseTable table = (DatabaseTable)tables.nextElement(); | |
SQLSelectStatement sqlStatement = new SQLSelectStatement(); | |
sqlStatement.addTable(table); | |
if (table == this.descriptor.getTables().firstElement()) { | |
sqlStatement.setWhereClause((Expression)getPrimaryKeyExpression().clone()); | |
} else { | |
sqlStatement.setWhereClause(buildPrimaryKeyExpression(table)); | |
} | |
DatabaseField all = new DatabaseField("*"); | |
all.setTable(table); | |
sqlStatement.addField(all); | |
sqlStatement.normalize(session, null); | |
DataReadQuery dataReadQuery = new DataReadQuery(); | |
dataReadQuery.setSQLStatement(sqlStatement); | |
dataReadQuery.setSessionName(this.descriptor.getSessionName()); | |
// execute the query and check if there is a valid result | |
List queryResults = (List)session.executeQuery(dataReadQuery, translationRow); | |
if (!queryResults.isEmpty()) { | |
return false; | |
} | |
} | |
} | |
// now ask each of the mappings to verify that the object has been deleted. | |
for (Enumeration mappings = this.descriptor.getMappings().elements(); | |
mappings.hasMoreElements();) { | |
DatabaseMapping mapping = (DatabaseMapping)mappings.nextElement(); | |
if (!mapping.verifyDelete(object, session)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* Return if the descriptor has a wrapper policy. | |
* Cache for performance. | |
*/ | |
public boolean hasWrapperPolicy() { | |
return hasWrapperPolicy; | |
} | |
/** | |
* Set if the descriptor has a wrapper policy. | |
* Cached for performance. | |
*/ | |
public void setHasWrapperPolicy(boolean hasWrapperPolicy) { | |
this.hasWrapperPolicy = hasWrapperPolicy; | |
} | |
/** | |
* Wrap the object if required. | |
* This is used for the wrapper policy support and EJB. | |
*/ | |
public Object wrapObject(Object implementation, AbstractSession session) { | |
if (!this.hasWrapperPolicy) { | |
return implementation; | |
} | |
if (implementation == null) { | |
return null; | |
} | |
// PERF: Using direct variable access. | |
// Check if already wrapped. | |
if ((!this.descriptor.hasWrapperPolicy()) || this.descriptor.getWrapperPolicy().isWrapped(implementation)) { | |
return implementation; | |
} | |
// Allow for inheritance, the concrete wrapper must always be used. | |
if (this.descriptor.hasInheritance() && this.descriptor.getInheritancePolicy().hasChildren() && (implementation.getClass() != this.descriptor.getJavaClass())) { | |
ClassDescriptor descriptor = session.getDescriptor(implementation); | |
if (descriptor != this.descriptor) { | |
return descriptor.getObjectBuilder().wrapObject(implementation, session); | |
} | |
} | |
return this.descriptor.getWrapperPolicy().wrapObject(implementation, session); | |
} | |
public boolean isXMLObjectBuilder() { | |
return false; | |
} | |
public String getLockAttribute() { | |
return this.lockAttribute; | |
} | |
public boolean shouldKeepRow() { | |
return this.shouldKeepRow; | |
} | |
public boolean hasCacheIndexesInSopObject() { | |
return this.hasCacheIndexesInSopObject; | |
} | |
} |