org.eclipse.persistence.internal.descriptors.ObjectBuilder Maven / Gradle / Ivy
Show all versions of eclipselink Show documentation
/*
* Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// 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
// 01/15/2016-2.7 Mythily Parthasarathy
// - 485984: Retrieve FetchGroup info along with getReference() from cache
// 08/07/2016-2.7 Dalia Abo Sheasha
// - 499335: Multiple embeddable fields can't reference same object
// 02/20/2018-2.7 Will Dazey
// - 529602: Added support for CLOBs in DELETE statements for Oracle
package org.eclipse.persistence.internal.descriptors;
import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.Semaphore;
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.DatabaseException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
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.DatasourcePlatform;
import org.eclipse.persistence.internal.databaseaccess.Platform;
import org.eclipse.persistence.internal.expressions.ObjectExpression;
import org.eclipse.persistence.internal.expressions.QueryKeyExpression;
import org.eclipse.persistence.internal.expressions.SQLSelectStatement;
import org.eclipse.persistence.internal.helper.ConcurrencySemaphore;
import org.eclipse.persistence.internal.helper.ConcurrencyUtil;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.helper.IdentityHashSet;
import org.eclipse.persistence.internal.helper.InvalidObject;
import org.eclipse.persistence.internal.helper.ThreadCursoredList;
import org.eclipse.persistence.internal.identitymaps.CacheId;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
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.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.AggregateChangeRecord;
import org.eclipse.persistence.internal.sessions.AggregateObjectChangeSet;
import org.eclipse.persistence.internal.sessions.ArrayRecord;
import org.eclipse.persistence.internal.sessions.ChangeRecord;
import org.eclipse.persistence.internal.sessions.DirectToFieldChangeRecord;
import org.eclipse.persistence.internal.sessions.MergeManager;
import org.eclipse.persistence.internal.sessions.ObjectChangeSet;
import org.eclipse.persistence.internal.sessions.ResultSetRecord;
import org.eclipse.persistence.internal.sessions.SimpleResultSetRecord;
import org.eclipse.persistence.internal.sessions.TransformationMappingChangeRecord;
import org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.mappings.AggregateObjectMapping;
import org.eclipse.persistence.mappings.ContainerMapping;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.DatabaseMapping.WriteType;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
import org.eclipse.persistence.mappings.ObjectReferenceMapping;
import org.eclipse.persistence.mappings.foundation.AbstractColumnMapping;
import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping;
import org.eclipse.persistence.mappings.foundation.AbstractTransformationMapping;
import org.eclipse.persistence.mappings.querykeys.DirectQueryKey;
import org.eclipse.persistence.mappings.querykeys.QueryKey;
import org.eclipse.persistence.oxm.XMLContext;
import org.eclipse.persistence.queries.AttributeGroup;
import org.eclipse.persistence.queries.DataReadQuery;
import org.eclipse.persistence.queries.FetchGroup;
import org.eclipse.persistence.queries.FetchGroupTracker;
import org.eclipse.persistence.queries.LoadGroup;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.eclipse.persistence.queries.ObjectLevelModifyQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.QueryByExamplePolicy;
import org.eclipse.persistence.queries.ReadAllQuery;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.eclipse.persistence.queries.WriteObjectQuery;
import org.eclipse.persistence.sessions.CopyGroup;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.sessions.SessionProfiler;
import org.eclipse.persistence.sessions.remote.DistributedSession;
/**
* Purpose: 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 implements Cloneable, Serializable {
protected ClassDescriptor descriptor;
/** Mappings keyed by attribute name. */
protected Map mappingsByAttribute;
/** Mappings keyed by database field. */
protected Map mappingsByField;
/** List of read-only mappings using a database field. */
protected Map> readOnlyMappingsByField;
/** Used to maintain identity on the field objects. Ensure they get the correct index/type. */
protected Map fieldsMap;
/** Mapping for the primary key fields. */
protected List primaryKeyMappings;
/** The types for the primary key fields, in same order as descriptor's primary key fields. */
protected List primaryKeyClassifications;
/** All mapping other than primary key mappings. */
protected transient List nonPrimaryKeyMappings;
/** Expression for querying an object by primary key. */
protected transient Expression primaryKeyExpression;
/** PERF: Cache mapping that use joining. */
protected List joinedAttributes;
/** PERF: Cache mapping that use batch fetching. */
protected List batchFetchedAttributes;
/** PERF: Cache mapping that use batch fetching. */
protected boolean hasInBatchFetchedAttribute;
/** PERF: Cache mappings that require cloning. */
protected List cloningMappings;
/** PERF: Cache mappings that are eager loaded. */
protected List eagerMappings;
/** PERF: Cache relationship mappings. */
protected List 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;
/** Semaphore related properties. Transient to avoid serialization in clustered/replicated environments see CORBA tests*/
private static final transient ThreadLocal SEMAPHORE_THREAD_LOCAL_VAR = new ThreadLocal<>();
private static final transient int SEMAPHORE_MAX_NUMBER_THREADS = ConcurrencyUtil.SINGLETON.getNoOfThreadsAllowedToObjectBuildInParallel();
private static final transient Semaphore SEMAPHORE_LIMIT_MAX_NUMBER_OF_THREADS_OBJECT_BUILDING = new Semaphore(SEMAPHORE_MAX_NUMBER_THREADS);
private transient ConcurrencySemaphore objectBuilderSemaphore = new ConcurrencySemaphore(SEMAPHORE_THREAD_LOCAL_VAR, SEMAPHORE_MAX_NUMBER_THREADS, SEMAPHORE_LIMIT_MAX_NUMBER_OF_THREADS_OBJECT_BUILDING, this, "object_builder_semaphore_acquired_01");
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.
*/
@Override
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 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, null, 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(), null, writeQuery.getSession(), writeQuery);
}
/**
* 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, Object sequenceValue) throws DatabaseException {
return assignSequenceNumber(writeQuery.getObject(), sequenceValue, 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, Object sequenceValue, 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.
int index = this.descriptor.getPrimaryKeyFields().indexOf(sequenceNumberField);
if (isPrimaryKeyComponentInvalid(existingValue, index) || this.descriptor.getSequence().shouldAlwaysOverrideExistingValue()) {
// If no sequence value was passed, obtain one from the Sequence
if(sequenceValue == null) {
sequenceValue = writeSession.getSequencing().getNextValue(this.descriptor.getJavaClass());
}
}
// 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.
*/
@Override
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.
* This is wrapper method with semaphore logic.
*/
public Object buildObject(ObjectBuildingQuery query, AbstractRecord databaseRow, JoinedAttributeManager joinManager,
AbstractSession session, ClassDescriptor concreteDescriptor, InheritancePolicy inheritancePolicy, boolean isUnitOfWork,
boolean shouldCacheQueryResults, boolean shouldUseWrapperPolicy) {
boolean semaphoreWasAcquired = false;
boolean useSemaphore = ConcurrencyUtil.SINGLETON.isUseSemaphoreInObjectBuilder();
if (objectBuilderSemaphore == null) {
objectBuilderSemaphore = new ConcurrencySemaphore(SEMAPHORE_THREAD_LOCAL_VAR, SEMAPHORE_MAX_NUMBER_THREADS, SEMAPHORE_LIMIT_MAX_NUMBER_OF_THREADS_OBJECT_BUILDING, this, "object_builder_semaphore_acquired_01");
}
try {
semaphoreWasAcquired = objectBuilderSemaphore.acquireSemaphoreIfAppropriate(useSemaphore);
return buildObjectInternal(query, databaseRow, joinManager, session, concreteDescriptor, inheritancePolicy, isUnitOfWork, shouldCacheQueryResults, shouldUseWrapperPolicy);
} finally {
objectBuilderSemaphore.releaseSemaphoreAllowOtherThreadsToStartDoingObjectBuilding(semaphoreWasAcquired);
}
}
/**
* Return an instance of the receivers javaClass. Set the attributes of an instance
* from the values stored in the database row.
*/
private Object buildObjectInternal(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() {
@Override
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.
*
* 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.
*
* 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.
*
* 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 = query.getLoadGroup();
if (group != null) {
session.load(domainObject, group, query.getDescriptor(), false);
}
}
if (session.getProject().allowExtendedCacheLogging() && cacheKey != null && cacheKey.getObject() != null) {
session.log(SessionLog.FINEST, SessionLog.CACHE, "cache_item_creation", new Object[] {domainObject.getClass(), primaryKey, Thread.currentThread().getId(), Thread.currentThread().getName()});
}
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 (session.getProject().allowExtendedCacheLogging() && cacheKey != null && cacheKey.getObject() != null) {
session.log(SessionLog.FINEST, SessionLog.CACHE, "cache_item_creation", new Object[] {protectedObject.getClass(), primaryKey, Thread.currentThread().getId(), Thread.currentThread().getName()});
}
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 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, 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, 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 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, 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, 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 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 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 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 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) {
if (wasAnOriginal) {
//485984: Save the FetchGroup from the original
fetchGroupManager.setObjectFetchGroup(workingClone, fetchGroupManager.getObjectFetchGroup(original), unitOfWork);
} else {
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.
* This is wrapper method with semaphore logic.
*/
public Object buildObjectFromResultSet(ObjectBuildingQuery query, JoinedAttributeManager joinManager, ResultSet resultSet, AbstractSession executionSession, DatabaseAccessor accessor, ResultSetMetaData metaData, DatabasePlatform platform, Vector fieldsList, DatabaseField[] fieldsArray) throws SQLException {
boolean semaphoreWasAcquired = false;
boolean useSemaphore = ConcurrencyUtil.SINGLETON.isUseSemaphoreInObjectBuilder();
if (objectBuilderSemaphore == null) {
objectBuilderSemaphore = new ConcurrencySemaphore(SEMAPHORE_THREAD_LOCAL_VAR, SEMAPHORE_MAX_NUMBER_THREADS, SEMAPHORE_LIMIT_MAX_NUMBER_OF_THREADS_OBJECT_BUILDING, this, "object_builder_semaphore_acquired_01");
}
try {
semaphoreWasAcquired = objectBuilderSemaphore.acquireSemaphoreIfAppropriate(useSemaphore);
return buildObjectFromResultSetInternal(query, joinManager, resultSet, executionSession, accessor, metaData, platform, fieldsList, fieldsArray);
} finally {
objectBuilderSemaphore.releaseSemaphoreAllowOtherThreadsToStartDoingObjectBuilding(semaphoreWasAcquired);
}
}
/**
* 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.
*/
private Object buildObjectFromResultSetInternal(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.
*/
@Override
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 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 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 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 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 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 visitedCopyGroups = (Set)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 attributesToVisit = copyGroup.getAttributeNames();
// Attributes to be copied
Set 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 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 || changes.getDescriptor() != this.descriptor) {
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 = primaryKeyFields.get(index);
String fieldClassificationClassName = null;
if (this.getBaseMappingForField(primaryKeyField) instanceof AbstractDirectMapping) {
fieldClassificationClassName = ((AbstractDirectMapping)this.getBaseMappingForField(primaryKeyField)).getFieldClassificationClassName();
}
subExpression = ((DatasourcePlatform)session.getDatasourcePlatform()).createExpressionFor(primaryKeyField, builder, fieldClassificationClassName);
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 keyFields = this.descriptor.getPrimaryKeyFields();
int size = keyFields.size();
Set 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 primaryKeyFields = descriptor.getPrimaryKeyFields();
Object[] primaryKeyValues = null;
if (cacheKeyType != CacheKeyType.ID_VALUE) {
primaryKeyValues = new Object[primaryKeyFields.size()];
}
List 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);
Set writtenMappings = new HashSet<>(size);
// PERF: use index not enumeration
for (int index = 0; index < size; index++) {
DatabaseMapping mapping = mappings.get(index);
// Bug 489783 - PERF: only write a PK mapping once when iterating
// Primary key mapping may be null for aggregate collection.
if (mapping != null && !writtenMappings.contains(mapping)) {
mapping.writeFromObjectIntoRow(domainObject, databaseRow, session, WriteType.UNDEFINED);
writtenMappings.add(mapping);
}
}
List 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 primaryKeyFields = this.descriptor.getPrimaryKeyFields();
if(null == primaryKeyFields) {
return null;
}
List 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 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 = 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 = 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 = 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 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 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 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 getEagerMappings() {
return eagerMappings;
}
/**
* Answers the attributes which are always joined to the original query on reads.
*/
public List getJoinedAttributes() {
return joinedAttributes;
}
/**
* Return the mappings that are always batch fetched.
*/
public List 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.
*/
@Override
public DatabaseMapping getMappingForField(DatabaseField field) {
return getMappingsByField().get(field);
}
/**
* Return all the read-only mapping for the specified field.
*/
public List getReadOnlyMappingsForField(DatabaseField field) {
return getReadOnlyMappingsByField().get(field);
}
/**
* Return all the mapping to attribute associations
*/
protected Map getMappingsByAttribute() {
return mappingsByAttribute;
}
/**
* INTERNAL:
* Return all the mapping to field associations
*/
public Map getMappingsByField() {
return mappingsByField;
}
/**
* INTERNAL:
* Return all the read-only mapping to field associations
*/
public Map> getReadOnlyMappingsByField() {
return readOnlyMappingsByField;
}
/**
* Return the non primary key mappings.
*/
protected List 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 = 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 getPrimaryKeyClassifications() {
if (primaryKeyClassifications == null) {
List primaryKeyFields = this.descriptor.getPrimaryKeyFields();
if(null == primaryKeyFields) {
return Collections.emptyList();
}
List 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 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 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 = 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 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.
*
* 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 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.
*
* By caching the result query execution in the case where there are no batch fetched
* attributes can be improved.
*/
public void initializeBatchFetchedAttributes() {
List 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 it = getDescriptor().getFields().iterator();
while(it.hasNext()) {
DatabaseField field = it.next();
if(field.getTable().equals(defaultTable) && getMappingsByField().containsKey(field)) {
primaryKeyFields.add(field);
}
}
List 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 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.
* An intermediary original is no longer needed.
*
This has ramifications to the copy policy and cmp, for clones are
* no longer built via cloning.
*
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 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 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);
}
}
if (session.getProject().allowExtendedCacheLogging() && cacheKey != null && cacheKey.getObject() != null) {
session.log(SessionLog.FINEST, SessionLog.CACHE, "cache_item_refresh", new Object[] {domainObject.getClass(), cacheKey.getKey(), Thread.currentThread().getId(), Thread.currentThread().getName()});
}
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 theAttributeMappings) {
mappingsByAttribute = theAttributeMappings;
}
/**
* INTERNAL:
* All the mappings and their respective field associations are cached for performance improvement.
*/
public void setMappingsByField(Map theFieldMappings) {
mappingsByField = theFieldMappings;
}
/**
* INTERNAL:
* All the read-only mappings and their respective field associations are cached for performance improvement.
*/
public void setReadOnlyMappingsByField(Map> theReadOnlyFieldMappings) {
readOnlyMappingsByField = theReadOnlyFieldMappings;
}
/**
* The non primary key mappings are cached to improve performance.
*/
protected void setNonPrimaryKeyMappings(List theNonPrimaryKeyMappings) {
nonPrimaryKeyMappings = theNonPrimaryKeyMappings;
}
/**
* INTERNAL:
* Set primary key classifications.
* These are used to ensure a consistent type for the pk values.
*/
public void setPrimaryKeyClassifications(List 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 thePrimaryKeyMappings) {
primaryKeyMappings = thePrimaryKeyMappings;
}
@Override
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;
}
@Override
public AbstractRecord createRecordFromXMLContext(XMLContext context) {
return createRecord((AbstractSession)context.getSession());
}
}