All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.internal.descriptors.ObjectBuilder Maven / Gradle / Ivy

There is a newer version: 5.0.0-B03
Show newest version
/*
 * 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()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy