| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 1998, 2021 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; |
| |
| /** |
| * <p><b>Purpose</b>: Object builder is one of the behavior class attached to descriptor. |
| * It is responsible for building objects, rows, and extracting primary keys from |
| * the object and the rows. |
| * |
| * @author Sati |
| * @since TOPLink/Java 1.0 |
| */ |
| public class ObjectBuilder extends CoreObjectBuilder<AbstractRecord, AbstractSession, DatabaseField, DatabaseMapping> implements Cloneable, Serializable { |
| protected ClassDescriptor descriptor; |
| /** Mappings keyed by attribute name. */ |
| protected Map<String, DatabaseMapping> mappingsByAttribute; |
| /** Mappings keyed by database field. */ |
| protected Map<DatabaseField, DatabaseMapping> mappingsByField; |
| /** List of read-only mappings using a database field. */ |
| protected Map<DatabaseField, List<DatabaseMapping>> readOnlyMappingsByField; |
| /** Used to maintain identity on the field objects. Ensure they get the correct index/type. */ |
| protected Map<DatabaseField, DatabaseField> fieldsMap; |
| /** Mapping for the primary key fields. */ |
| protected List<DatabaseMapping> primaryKeyMappings; |
| /** The types for the primary key fields, in same order as descriptor's primary key fields. */ |
| protected List<Class> primaryKeyClassifications; |
| /** All mapping other than primary key mappings. */ |
| protected transient List<DatabaseMapping> nonPrimaryKeyMappings; |
| /** Expression for querying an object by primary key. */ |
| protected transient Expression primaryKeyExpression; |
| /** PERF: Cache mapping that use joining. */ |
| protected List<DatabaseMapping> joinedAttributes; |
| /** PERF: Cache mapping that use batch fetching. */ |
| protected List<DatabaseMapping> batchFetchedAttributes; |
| /** PERF: Cache mapping that use batch fetching. */ |
| protected boolean hasInBatchFetchedAttribute; |
| /** PERF: Cache mappings that require cloning. */ |
| protected List<DatabaseMapping> cloningMappings; |
| /** PERF: Cache mappings that are eager loaded. */ |
| protected List<DatabaseMapping> eagerMappings; |
| /** PERF: Cache relationship mappings. */ |
| protected List<DatabaseMapping> relationshipMappings; |
| /** PERF: Cache if is a simple mapping, all direct. */ |
| protected boolean isSimple; |
| /** PERF: Cache if has a wrapper policy. */ |
| protected boolean hasWrapperPolicy; |
| /** PERF: Cache sequence mappings. */ |
| protected AbstractDirectMapping sequenceMapping; |
| /** indicates whether part of primary key is unmapped - may happen only in case AggregateObject or AggregateCollection descriptor. */ |
| protected boolean mayHaveNullInPrimaryKey; |
| /** attribute name corresponding to optimistic lock field, set only if optimistic locking is used */ |
| protected String lockAttribute; |
| /** PERF: is there a mapping using indirection (could be nested in aggregate(s)), or any other reason to keep row after the object has been created. |
| Used by ObjectLevelReadQuery ResultSetAccessOptimization. */ |
| protected boolean shouldKeepRow = false; |
| /** PERF: is there an cache index field that's would not be selected by SOP query. Ignored unless descriptor uses SOP and CachePolicy has cache indexes. */ |
| protected boolean hasCacheIndexesInSopObject = false; |
| /** Semaphore related properties. Transient to avoid serialization in clustered/replicated environments see CORBA tests*/ |
| private static final transient ThreadLocal<Boolean> 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<DatabaseTable> tables = this.descriptor.getTables(); |
| int size = tables.size(); |
| |
| // Skip first table. |
| for (int index = 1; index < size; index++) { |
| DatabaseTable table = tables.get(index); |
| Map keyMapping = this.descriptor.getAdditionalTablePrimaryKeyFields().get(table); |
| |
| // Loop over the additionalTablePK fields and add the PK info for the table. The join might |
| // be between a fk in the source table and pk in secondary table. |
| if (keyMapping != null) { |
| Iterator primaryKeyFieldEnum = keyMapping.keySet().iterator(); |
| Iterator secondaryKeyFieldEnum = keyMapping.values().iterator(); |
| while (primaryKeyFieldEnum.hasNext()) { |
| DatabaseField primaryKeyField = (DatabaseField)primaryKeyFieldEnum.next(); |
| DatabaseField secondaryKeyField = (DatabaseField)secondaryKeyFieldEnum.next(); |
| Object primaryValue = databaseRow.getIndicatingNoEntry(primaryKeyField); |
| |
| // normally the primary key has a value, however if the multiple tables were joined by a foreign |
| // key the foreign key has a value. |
| if ((primaryValue == AbstractRecord.noEntry)) { |
| if (object != null) { |
| DatabaseMapping mapping = getMappingForField(secondaryKeyField); |
| if (mapping == null) { |
| throw DescriptorException.missingMappingForField(secondaryKeyField, this.descriptor); |
| } |
| mapping.writeFromObjectIntoRow(object, databaseRow, session, WriteType.UNDEFINED); |
| } |
| databaseRow.put(primaryKeyField, databaseRow.get(secondaryKeyField)); |
| } else { |
| databaseRow.put(secondaryKeyField, primaryValue); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Clear any primary key cache data in the object. |
| */ |
| public void clearPrimaryKey(Object object) { |
| // PERF: If PersistenceEntity is caching the primary key this must be cleared as the primary key has changed. |
| if (object instanceof PersistenceEntity) { |
| ((PersistenceEntity)object)._persistence_setId(null); |
| } |
| } |
| |
| /** |
| * Assign the fields in the row back into the object. |
| * This is used by returning, as well as events and version locking. |
| * If not null changeSet must correspond to object. changeSet is updated with all of the field values in the row. |
| */ |
| public void assignReturnRow(Object object, AbstractSession writeSession, AbstractRecord row, ObjectChangeSet changeSet) throws DatabaseException { |
| writeSession.log(SessionLog.FINEST, SessionLog.QUERY, "assign_return_row", row); |
| |
| // Require a query context to read into an object. |
| ReadObjectQuery query = new ReadObjectQuery(); |
| query.setSession(writeSession); |
| |
| // To avoid processing the same mapping twice, |
| // maintain Collection of mappings already used. |
| HashSet handledMappings = null; |
| int size = row.size(); |
| if (size > 1) { |
| handledMappings = new HashSet(size); |
| } |
| List fields = row.getFields(); |
| for (int index = 0; index < size; index++) { |
| DatabaseField field = (DatabaseField)fields.get(index); |
| assignReturnValueForField(object, query, row, field, handledMappings, changeSet); |
| } |
| } |
| |
| /** |
| * Assign the field value from the row to the object for all the mappings using field (read or write). |
| * If not null changeSet must correspond to object. changeSet is updated with all of the field values in the row. |
| */ |
| public void assignReturnValueForField(Object object, ReadObjectQuery query, AbstractRecord row, DatabaseField field, Collection handledMappings, ObjectChangeSet changeSet) { |
| DatabaseMapping mapping = getMappingForField(field); |
| if (mapping != null) { |
| assignReturnValueToMapping(object, query, row, field, mapping, handledMappings, changeSet); |
| } |
| List readOnlyMappings = getReadOnlyMappingsForField(field); |
| if (readOnlyMappings != null) { |
| int size = readOnlyMappings.size(); |
| for (int index = 0; index < size; index++) { |
| mapping = (DatabaseMapping)readOnlyMappings.get(index); |
| assignReturnValueToMapping(object, query, row, field, mapping, handledMappings, changeSet); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Assign values from objectRow to the object through the mapping. |
| * If not null changeSet must correspond to object. changeSet is updated with all of the field values in the row. |
| */ |
| protected void assignReturnValueToMapping(Object object, ReadObjectQuery query, AbstractRecord row, DatabaseField field, DatabaseMapping mapping, Collection handledMappings, ObjectChangeSet changeSet) { |
| if ((handledMappings != null) && handledMappings.contains(mapping)) { |
| return; |
| } |
| if (mapping.isAbstractDirectMapping()) { |
| if(changeSet != null && (!changeSet.isNew() || (query.getDescriptor() != null && query.getDescriptor().shouldUseFullChangeSetsForNewObjects()))) { |
| DirectToFieldChangeRecord changeRecord = (DirectToFieldChangeRecord)changeSet.getChangesForAttributeNamed(mapping.getAttributeName()); |
| Object oldAttributeValue = null; |
| if (changeRecord == null) { |
| oldAttributeValue = mapping.getAttributeValueFromObject(object); |
| } |
| |
| //use null cachekey to ensure we build directly into the attribute |
| Object attributeValue = mapping.readFromRowIntoObject(row, null, object, null, query, query.getSession(), true); |
| |
| if (changeRecord == null) { |
| // Don't use ObjectChangeSet.updateChangeRecordForAttributeWithMappedObject to avoid unnecessary conversion - attributeValue is already converted. |
| changeRecord = (DirectToFieldChangeRecord)((AbstractDirectMapping)mapping).internalBuildChangeRecord(attributeValue, oldAttributeValue, changeSet); |
| changeSet.addChange(changeRecord); |
| } else { |
| changeRecord.setNewValue(attributeValue); |
| } |
| } else { |
| mapping.readFromRowIntoObject(row, null, object, null, query, query.getSession(), true); |
| } |
| } else if (mapping.isAggregateObjectMapping()) { |
| ((AggregateObjectMapping)mapping).readFromReturnRowIntoObject(row, object, query, handledMappings, changeSet); |
| } else if (mapping.isTransformationMapping()) { |
| ((AbstractTransformationMapping)mapping).readFromReturnRowIntoObject(row, object, query, handledMappings, changeSet); |
| } else { |
| query.getSession().log(SessionLog.FINEST, SessionLog.QUERY, "field_for_unsupported_mapping_returned", field, this.descriptor); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Update the object primary key by fetching a new sequence number from the accessor. |
| * This assume the uses sequence numbers check has already been done. |
| * @return the sequence value or null if not assigned. |
| * @exception DatabaseException - an error has occurred on the database. |
| */ |
| public Object assignSequenceNumber(Object object, AbstractSession writeSession) throws DatabaseException { |
| return assignSequenceNumber(object, writeSession, null); |
| } |
| |
| /** |
| * INTERNAL: |
| * Update the writeQuery's object primary key by fetching a new sequence number from the accessor. |
| * This assume the uses sequence numbers check has already been done. |
| * Adds the assigned sequence value to writeQuery's modify row. |
| * If object has a changeSet then sets sequence value into change set as an Id |
| * adds it also to object's change set in a ChangeRecord if required. |
| * @return the sequence value or null if not assigned. |
| * @exception DatabaseException - an error has occurred on the database. |
| */ |
| public Object assignSequenceNumber(WriteObjectQuery writeQuery) throws DatabaseException { |
| return assignSequenceNumber(writeQuery.getObject(), writeQuery.getSession(), writeQuery); |
| } |
| |
| /** |
| * INTERNAL: |
| * Update the object primary key by fetching a new sequence number from the accessor. |
| * This assume the uses sequence numbers check has already been done. |
| * Adds the assigned sequence value to writeQuery's modify row. |
| * If object has a changeSet then sets sequence value into change set as an Id |
| * adds it also to object's change set in a ChangeRecord if required. |
| * @return the sequence value or null if not assigned. |
| * @exception DatabaseException - an error has occurred on the database. |
| */ |
| protected Object assignSequenceNumber(Object object, AbstractSession writeSession, WriteObjectQuery writeQuery) throws DatabaseException { |
| DatabaseField sequenceNumberField = this.descriptor.getSequenceNumberField(); |
| Object existingValue = null; |
| if (this.sequenceMapping != null) { |
| existingValue = this.sequenceMapping.getAttributeValueFromObject(object); |
| } else { |
| existingValue = getBaseValueForField(sequenceNumberField, object); |
| } |
| |
| // PERF: The (internal) support for letting the sequence decide this was removed, |
| // as anything other than primitive should allow null and default as such. |
| Object sequenceValue; |
| int index = this.descriptor.getPrimaryKeyFields().indexOf(sequenceNumberField); |
| if (isPrimaryKeyComponentInvalid(existingValue, index) || this.descriptor.getSequence().shouldAlwaysOverrideExistingValue()) { |
| sequenceValue = writeSession.getSequencing().getNextValue(this.descriptor.getJavaClass()); |
| } else { |
| return null; |
| } |
| |
| // Check that the value is not null, this occurs on any databases using IDENTITY type sequencing. |
| if (sequenceValue == null) { |
| return null; |
| } |
| |
| writeSession.log(SessionLog.FINEST, SessionLog.SEQUENCING, "assign_sequence", sequenceValue, object); |
| Object convertedSequenceValue = null; |
| if (this.sequenceMapping != null) { |
| convertedSequenceValue = this.sequenceMapping.getObjectValue(sequenceValue, writeSession); |
| this.sequenceMapping.setAttributeValueInObject(object, convertedSequenceValue); |
| } else { |
| // Now add the value to the object, this gets ugly. |
| AbstractRecord tempRow = createRecord(1, writeSession); |
| tempRow.put(sequenceNumberField, sequenceValue); |
| |
| // Require a query context to read into an object. |
| ReadObjectQuery query = new ReadObjectQuery(); |
| query.setSession(writeSession); |
| DatabaseMapping mapping = getBaseMappingForField(sequenceNumberField); |
| Object sequenceIntoObject = getParentObjectForField(sequenceNumberField, object); |
| |
| // The following method will return the converted value for the sequence. |
| convertedSequenceValue = mapping.readFromRowIntoObject(tempRow, null, sequenceIntoObject, null, query, writeSession, true); |
| } |
| |
| // PERF: If PersistenceEntity is caching the primary key this must be cleared as the primary key has changed. |
| clearPrimaryKey(object); |
| |
| if (writeQuery != null) { |
| Object primaryKey = extractPrimaryKeyFromObject(object, writeSession); |
| writeQuery.setPrimaryKey(primaryKey); |
| AbstractRecord modifyRow = writeQuery.getModifyRow(); |
| // Update the row. |
| modifyRow.put(sequenceNumberField, sequenceValue); |
| if (descriptor.hasMultipleTables()) { |
| addPrimaryKeyForNonDefaultTable(modifyRow, object, writeSession); |
| } |
| // Update the changeSet if there is one. |
| if (writeSession.isUnitOfWork()) { |
| ObjectChangeSet objectChangeSet = writeQuery.getObjectChangeSet(); |
| if ((objectChangeSet == null) && (((UnitOfWorkImpl)writeSession).getUnitOfWorkChangeSet() != null)) { |
| objectChangeSet = (ObjectChangeSet)((UnitOfWorkImpl)writeSession).getUnitOfWorkChangeSet().getObjectChangeSetForClone(object); |
| } |
| if (objectChangeSet != null) { |
| // objectChangeSet.isNew() == true |
| if (writeQuery.getDescriptor().shouldUseFullChangeSetsForNewObjects()) { |
| if (this.sequenceMapping != null) { |
| // Don't use ObjectChangeSet.updateChangeRecordForAttribute to avoid unnecessary conversion - convertedSequenceValue is already converted. |
| String attributeName = this.sequenceMapping.getAttributeName(); |
| DirectToFieldChangeRecord changeRecord = (DirectToFieldChangeRecord)objectChangeSet.getChangesForAttributeNamed(attributeName); |
| if (changeRecord == null) { |
| changeRecord = new DirectToFieldChangeRecord(objectChangeSet); |
| changeRecord.setAttribute(attributeName); |
| changeRecord.setMapping(this.sequenceMapping); |
| objectChangeSet.addChange(changeRecord); |
| } |
| changeRecord.setNewValue(convertedSequenceValue); |
| } else { |
| ChangeRecord changeRecord = getBaseChangeRecordForField(objectChangeSet, object, sequenceNumberField, writeSession); |
| if (changeRecord.getMapping().isDirectCollectionMapping()) { |
| // assign converted value to the attribute |
| ((DirectToFieldChangeRecord)changeRecord).setNewValue(convertedSequenceValue); |
| } else if (changeRecord.getMapping().isTransformationMapping()) { |
| // put original (not converted) value into the record. |
| ((TransformationMappingChangeRecord)changeRecord).getRecord().put(sequenceNumberField, sequenceValue); |
| } |
| } |
| } |
| objectChangeSet.setId(primaryKey); |
| } |
| } |
| } |
| |
| return convertedSequenceValue; |
| } |
| |
| /** |
| * Each mapping is recursed to assign values from the Record to the attributes in the domain object. |
| */ |
| public void buildAttributesIntoObject(Object domainObject, CacheKey cacheKey, AbstractRecord databaseRow, ObjectBuildingQuery query, JoinedAttributeManager joinManager, FetchGroup executionFetchGroup, boolean forRefresh, AbstractSession targetSession) throws DatabaseException { |
| if (this.descriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy()) { |
| if (buildAttributesIntoObjectSOP(domainObject, cacheKey, databaseRow, query, joinManager, executionFetchGroup, forRefresh, targetSession)) { |
| return; |
| } |
| } |
| // PERF: Avoid synchronized enumerator as is concurrency bottleneck. |
| List mappings = this.descriptor.getMappings(); |
| |
| // PERF: Cache if all mappings should be read. |
| boolean readAllMappings = query.shouldReadAllMappings(); |
| boolean isTargetProtected = targetSession.isProtectedSession(); |
| int size = mappings.size(); |
| for (int index = 0; index < size; index++) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); |
| if (readAllMappings || query.shouldReadMapping(mapping, executionFetchGroup)) { |
| mapping.readFromRowIntoObject(databaseRow, joinManager, domainObject, cacheKey, query, targetSession, isTargetProtected); |
| } |
| } |
| |
| // PERF: Avoid events if no listeners. |
| if (this.descriptor.hasEventManager()) { |
| postBuildAttributesIntoObjectEvent(domainObject, databaseRow, query, forRefresh); |
| } |
| } |
| |
| /** |
| * Each mapping is recursed to assign values from the Record to the attributes in the domain object. |
| * Should not be called unless (this.descriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy()) |
| * This method populates the object only in if some mappings potentially should be read using sopObject and other mappings - not using it. |
| * That happens when the row has been just read from the database and potentially has serialized object still in deserialized bits as a field value. |
| * Note that domainObject == sopObject is the same case, but (because domainObject has to be set into cache beforehand) extraction of sopObject |
| * from bit was done right before this method is called. |
| * Alternative situation is processing an empty row that has been created by foreign reference mapping |
| * and holds nothing but sopObject (which is an attribute of the original sopObject) - this case falls through to buildAttributesIntoObject. |
| * If attempt to deserialize sopObject from bits has failed, but SOP was setup to allow recovery |
| * (all mapped all fields/value mapped to the object were read, not just those excluded from SOP) |
| * then fall through to buildAttributesIntoObject. |
| * Nothing should be done if sopObject is not null, but domainObject != sopObject: |
| * the only way to get into this case should be with original query not maintaining cache, |
| * through a back reference to the original object, which is already being built (or has been built). |
| * @return whether the object has been populated with attributes, if not then buildAttributesIntoObject should be called. |
| */ |
| protected boolean buildAttributesIntoObjectSOP(Object domainObject, CacheKey cacheKey, AbstractRecord databaseRow, ObjectBuildingQuery query, JoinedAttributeManager joinManager, FetchGroup executionFetchGroup, boolean forRefresh, AbstractSession targetSession) throws DatabaseException { |
| Object sopObject = databaseRow.getSopObject(); |
| if (domainObject == sopObject) { |
| // PERF: Cache if all mappings should be read. |
| boolean readAllMappings = query.shouldReadAllMappings(); |
| boolean isTargetProtected = targetSession.isProtectedSession(); |
| // domainObject is sopObject |
| for (DatabaseMapping mapping : this.descriptor.getMappings()) { |
| if (readAllMappings || query.shouldReadMapping(mapping, executionFetchGroup)) { |
| // to avoid re-setting the same attribute value to domainObject |
| // only populate if either mapping (possibly nested) may reference entity or mapping does not use sopObject |
| if (mapping.hasNestedIdentityReference() || mapping.isOutOnlySopObject()) { |
| if (mapping.isOutSopObject()) { |
| // the mapping should be processed as if there is no sopObject |
| databaseRow.setSopObject(null); |
| } else { |
| databaseRow.setSopObject(sopObject); |
| } |
| mapping.readFromRowIntoObject(databaseRow, joinManager, domainObject, cacheKey, query, targetSession, isTargetProtected); |
| } |
| } |
| } |
| // PERF: Avoid events if no listeners. |
| if (this.descriptor.hasEventManager()) { |
| postBuildAttributesIntoObjectEvent(domainObject, databaseRow, query, forRefresh); |
| } |
| // sopObject has been processed by all relevant mappings, no longer required. |
| databaseRow.setSopObject(null); |
| return true; |
| } else { |
| if (sopObject == null) { |
| // serialized sopObject is a value corresponding to sopField in the row, row.sopObject==null; |
| // the following line sets deserialized sopObject into row.sopObject variable and sets sopField's value to null; |
| sopObject = this.descriptor.getSerializedObjectPolicy().getObjectFromRow(databaseRow, targetSession, (ObjectLevelReadQuery)query); |
| if (sopObject != null) { |
| // PERF: Cache if all mappings should be read. |
| boolean readAllMappings = query.shouldReadAllMappings(); |
| boolean isTargetProtected = targetSession.isProtectedSession(); |
| for (DatabaseMapping mapping : this.descriptor.getMappings()) { |
| if (readAllMappings || query.shouldReadMapping(mapping, executionFetchGroup)) { |
| if (mapping.isOutSopObject()) { |
| // the mapping should be processed as if there is no sopObject |
| databaseRow.setSopObject(null); |
| } else { |
| databaseRow.setSopObject(sopObject); |
| } |
| mapping.readFromRowIntoObject(databaseRow, joinManager, domainObject, cacheKey, query, targetSession, isTargetProtected); |
| } |
| } |
| // PERF: Avoid events if no listeners. |
| if (this.descriptor.hasEventManager()) { |
| postBuildAttributesIntoObjectEvent(domainObject, databaseRow, query, forRefresh); |
| } |
| // sopObject has been processed by all relevant mappings, no longer required. |
| databaseRow.setSopObject(null); |
| return true; |
| } else { |
| // SOP.getObjectFromRow returned null means serialized bits for sopObject either missing or deserilaized sopObject is invalid. |
| // If the method hasn't thrown exception then populating of the object is possible from the regular fields/values of the row |
| // (that means all fields/value mapped to the object were read, not just those excluded from SOP). |
| // return false and fall through to buildAttributesIntoObject |
| return false; |
| } |
| } else { |
| // A mapping under SOP can't have another SOP on its reference descriptor, |
| // but that's what seem to be happening. |
| // The only way to get here should be with original query not maintaining cache, |
| // through a back reference to the original object, which is already being built (or has been built). |
| // Leave without building. |
| return true; |
| } |
| } |
| } |
| |
| protected void postBuildAttributesIntoObjectEvent(Object domainObject, AbstractRecord databaseRow, ObjectBuildingQuery query, boolean forRefresh) { |
| DescriptorEventManager descriptorEventManager = this.descriptor.getDescriptorEventManager(); |
| if(descriptorEventManager.hasAnyEventListeners()) { |
| // Need to run post build or refresh selector, currently check with the query for this, |
| // I'm not sure which should be called it case of refresh building a new object, currently refresh is used... |
| org.eclipse.persistence.descriptors.DescriptorEvent event = new DescriptorEvent(domainObject); |
| event.setQuery(query); |
| event.setSession(query.getSession()); |
| event.setRecord(databaseRow); |
| if (forRefresh) { |
| //this method can be called from different places within TopLink. We may be |
| //executing refresh query but building the object not refreshing so we must |
| //throw the appropriate event. |
| //bug 3325315 |
| event.setEventCode(DescriptorEventManager.PostRefreshEvent); |
| } else { |
| event.setEventCode(DescriptorEventManager.PostBuildEvent); |
| } |
| descriptorEventManager.executeEvent(event); |
| } |
| } |
| |
| /** |
| * Returns the backup clone of the specified object. This is called only from unit of work. |
| * The clone sent as parameter is always a working copy from the unit of work. |
| */ |
| public Object buildBackupClone(Object clone, UnitOfWorkImpl unitOfWork) { |
| // The copy policy builds clone . |
| ClassDescriptor descriptor = this.descriptor; |
| Object backup = descriptor.getCopyPolicy().buildClone(clone, unitOfWork); |
| |
| // PERF: Avoid synchronized enumerator as is concurrency bottleneck. |
| List mappings = getCloningMappings(); |
| int size = mappings.size(); |
| if (descriptor.hasFetchGroupManager() && descriptor.getFetchGroupManager().isPartialObject(clone)) { |
| FetchGroupManager fetchGroupManager = descriptor.getFetchGroupManager(); |
| for (int index = 0; index < size; index++) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); |
| if (fetchGroupManager.isAttributeFetched(clone, mapping.getAttributeName())) { |
| mapping.buildBackupClone(clone, backup, unitOfWork); |
| } |
| } |
| } else { |
| for (int index = 0; index < size; index++) { |
| ((DatabaseMapping)mappings.get(index)).buildBackupClone(clone, backup, unitOfWork); |
| } |
| } |
| |
| return backup; |
| } |
| |
| /** |
| * Build and return the expression to use as the where clause to delete an object. |
| * The row is passed to allow the version number to be extracted from it. |
| * If called with usesOptimisticLocking==true the caller should make sure that descriptor uses optimistic locking policy. |
| */ |
| public Expression buildDeleteExpression(DatabaseTable table, AbstractRecord row, boolean usesOptimisticLocking) { |
| if (usesOptimisticLocking && (this.descriptor.getTables().firstElement().equals(table))) { |
| return this.descriptor.getOptimisticLockingPolicy().buildDeleteExpression(table, primaryKeyExpression, row); |
| } else { |
| return buildPrimaryKeyExpression(table); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used when Query By Example is used. Going through the mappings one by one, this method |
| * calls the specific buildExpression method corresponding to the type of mapping. It then generates a |
| * complete Expression by joining the individual Expressions. |
| */ |
| public Expression buildExpressionFromExample(Object queryObject, QueryByExamplePolicy policy, Expression expressionBuilder, Map processedObjects, AbstractSession session) { |
| if (processedObjects.containsKey(queryObject)) { |
| //this object has already been queried on |
| return null; |
| } |
| |
| processedObjects.put(queryObject, queryObject); |
| Expression expression = null; |
| |
| // PERF: Avoid synchronized enumerator as is concurrency bottleneck. |
| List mappings = this.descriptor.getMappings(); |
| for (int index = 0; index < mappings.size(); index++) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); |
| if (expression == null) { |
| expression = mapping.buildExpression(queryObject, policy, expressionBuilder, processedObjects, session); |
| } else { |
| expression = expression.and(mapping.buildExpression(queryObject, policy, expressionBuilder, processedObjects, session)); |
| } |
| } |
| |
| return expression; |
| } |
| |
| /** |
| * Return a new instance of the receiver's javaClass. |
| */ |
| @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. |
| * <p> |
| * Decides whether to call either buildWorkingCopyCloneFromRow (bypassing |
| * shared cache) or buildWorkingCopyCloneNormally (placing the result in the |
| * shared cache). |
| */ |
| protected Object buildObjectInUnitOfWork(ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, Object primaryKey, CacheKey preFetchedCacheKey, ClassDescriptor concreteDescriptor) throws DatabaseException, QueryException { |
| // When in transaction we are reading via the write connection |
| // and so do not want to corrupt the shared cache with dirty objects. |
| // Hence we build and refresh clones directly from the database row. |
| // PERF: Allow the session cached to still be used after early transaction if isolation setting has been set. |
| CachePolicy cachePolicy = concreteDescriptor.getCachePolicy(); |
| if (!cachePolicy.shouldUseSessionCacheInUnitOfWorkEarlyTransaction()) { |
| if (((unitOfWork.hasCommitManager() && unitOfWork.getCommitManager().isActive()) |
| || unitOfWork.wasTransactionBegunPrematurely() |
| || cachePolicy.shouldIsolateObjectsInUnitOfWork() |
| || cachePolicy.shouldIsolateProtectedObjectsInUnitOfWork() |
| || query.shouldStoreBypassCache()) |
| && (!unitOfWork.isClassReadOnly(concreteDescriptor.getJavaClass(), concreteDescriptor))) { |
| // It is easier to switch once to the correct builder here. |
| return concreteDescriptor.getObjectBuilder().buildWorkingCopyCloneFromRow(query, joinManager, databaseRow, unitOfWork, primaryKey, preFetchedCacheKey); |
| } |
| } |
| |
| return buildWorkingCopyCloneNormally(query, databaseRow, unitOfWork, primaryKey, preFetchedCacheKey, concreteDescriptor, joinManager); |
| } |
| |
| /** |
| * buildWorkingCopyCloneFromRow is an alternative to this which is the |
| * normal behavior. |
| * A row is read from the database, an original is built/refreshed/returned |
| * from the shared cache, and the original is registered/conformed/reverted |
| * in the UnitOfWork. |
| * <p> |
| * This default behavior is only safe when the query is executed on a read |
| * connection, otherwise uncommitted data might get loaded into the shared |
| * cache. |
| * <p> |
| * Represents the way TopLink has always worked. |
| */ |
| protected Object buildWorkingCopyCloneNormally(ObjectBuildingQuery query, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, Object primaryKey, CacheKey preFetchedCacheKey, ClassDescriptor concreteDescriptor, JoinedAttributeManager joinManager) throws DatabaseException, QueryException { |
| // First check local unit of work cache. |
| CacheKey unitOfWorkCacheKey = unitOfWork.getIdentityMapAccessorInstance().acquireLock(primaryKey, concreteDescriptor.getJavaClass(), concreteDescriptor, query.isCacheCheckComplete()); |
| Object clone = unitOfWorkCacheKey.getObject(); |
| boolean found = clone != null; |
| Object original = null; |
| try { |
| // Only check parent cache if not in unit of work, or if a refresh is required. |
| if (!found || query.shouldRefreshIdentityMapResult() |
| || query.shouldCacheQueryResults() // Need to build original to cache it. |
| || query.shouldRetrieveBypassCache() |
| || (concreteDescriptor.hasFetchGroupManager() && concreteDescriptor.getFetchGroupManager().isPartialObject(clone))) { |
| // This is normal case when we are not in transaction. |
| // Pass the query off to the parent. Let it build the object and |
| // cache it normally, then register/refresh it. |
| AbstractSession session = unitOfWork.getParentIdentityMapSession(query); |
| |
| // forwarding queries to different sessions is now as simple as setting |
| // the session on the query. |
| query.setSession(session); |
| if (session.isUnitOfWork()) { |
| // If a nested unit of work, recurse. |
| original = buildObjectInUnitOfWork(query, joinManager, databaseRow, (UnitOfWorkImpl)session, primaryKey, preFetchedCacheKey, concreteDescriptor); |
| //GFBug#404 Pass in joinManager or not based on if shouldCascadeCloneToJoinedRelationship is set to true |
| if (unitOfWork.shouldCascadeCloneToJoinedRelationship()) { |
| return query.registerIndividualResult(original, primaryKey, unitOfWork, joinManager, concreteDescriptor); |
| } else { |
| return query.registerIndividualResult(original, primaryKey, unitOfWork, null, concreteDescriptor); |
| } |
| } else { |
| // PERF: This optimizes the normal case to avoid duplicate cache access. |
| CacheKey parentCacheKey = (CacheKey)buildObject(true, query, databaseRow, session, primaryKey, preFetchedCacheKey, concreteDescriptor, joinManager); |
| original = parentCacheKey.getObject(); |
| if (query.shouldCacheQueryResults()) { |
| query.cacheResult(original); |
| } |
| // PERF: Do not register nor process read-only. |
| if (unitOfWork.isClassReadOnly(original.getClass(), concreteDescriptor)) { |
| // There is an obscure case where they object could be read-only and pessimistic. |
| // Record clone if referenced class has pessimistic locking policy. |
| query.recordCloneForPessimisticLocking(original, unitOfWork); |
| return original; |
| } |
| if (!query.isRegisteringResults()) { |
| return original; |
| } |
| if (clone == null) { |
| clone = unitOfWork.cloneAndRegisterObject(original, parentCacheKey, unitOfWorkCacheKey, concreteDescriptor); |
| // TODO-dclarke: At this point the clones do not have their fetch-group specified |
| // relationship attributes loaded |
| } |
| //bug3659327 |
| //fetch group manager control fetch group support |
| if (concreteDescriptor.hasFetchGroupManager()) { |
| //if the object is already registered in uow, but it's partially fetched (fetch group case) |
| if (concreteDescriptor.getFetchGroupManager().shouldWriteInto(original, clone)) { |
| //there might be cases when reverting/refreshing clone is needed. |
| concreteDescriptor.getFetchGroupManager().writePartialIntoClones(original, clone, unitOfWork.getBackupClone(clone, concreteDescriptor), unitOfWork); |
| } |
| } |
| } |
| } |
| query.postRegisterIndividualResult(clone, original, primaryKey, unitOfWork, joinManager, concreteDescriptor); |
| } finally { |
| unitOfWorkCacheKey.release(); |
| query.setSession(unitOfWork); |
| } |
| return clone; |
| } |
| |
| /** |
| * Return an instance of the receivers javaClass. Set the attributes of an instance |
| * from the values stored in the database row. |
| */ |
| protected Object buildObject(boolean returnCacheKey, ObjectBuildingQuery query, AbstractRecord databaseRow, AbstractSession session, Object primaryKey, CacheKey preFetchedCacheKey, ClassDescriptor concreteDescriptor, JoinedAttributeManager joinManager) throws DatabaseException, QueryException { |
| boolean isProtected = concreteDescriptor.getCachePolicy().isProtectedIsolation(); |
| if (isProtected && session.isIsolatedClientSession()){ |
| return buildProtectedObject(returnCacheKey, query, databaseRow, session, primaryKey, preFetchedCacheKey, concreteDescriptor, joinManager); |
| } |
| Object domainObject = null; |
| |
| // Cache key is used for object locking. |
| CacheKey cacheKey = null; |
| // Keep track if we actually built/refresh the object. |
| boolean cacheHit = true; |
| boolean isSopQuery = concreteDescriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy(); |
| // has to cache this flag - sopObject is set to null in the row after it has been processed |
| boolean hasSopObject = databaseRow.hasSopObject(); |
| boolean domainWasMissing = true; |
| boolean shouldMaintainCache = query.shouldMaintainCache(); |
| ObjectBuilder concreteObjectBuilder = concreteDescriptor.getObjectBuilder(); |
| try { |
| boolean shouldRetrieveBypassCache = query.shouldRetrieveBypassCache(); |
| boolean shouldStoreBypassCache = query.shouldStoreBypassCache(); |
| // Check if the objects exists in the identity map. |
| if (shouldMaintainCache && (!shouldRetrieveBypassCache || !shouldStoreBypassCache)) { |
| if (preFetchedCacheKey == null){ |
| cacheKey = session.retrieveCacheKey(primaryKey, concreteDescriptor, joinManager, query); |
| }else{ |
| cacheKey = preFetchedCacheKey; |
| cacheKey.acquireLock(query); |
| } |
| if (cacheKey != null){ |
| domainObject = cacheKey.getObject(); |
| } |
| domainWasMissing = domainObject == null; |
| } |
| |
| FetchGroup fetchGroup = query.getExecutionFetchGroup(concreteDescriptor); |
| |
| if (domainWasMissing || shouldRetrieveBypassCache) { |
| cacheHit = false; |
| if (domainObject == null || shouldStoreBypassCache) { |
| if (query.isReadObjectQuery() && ((ReadObjectQuery)query).shouldLoadResultIntoSelectionObject()) { |
| domainObject = ((ReadObjectQuery)query).getSelectionObject(); |
| } else { |
| if (isSopQuery && !hasSopObject) { |
| // serialized sopObject is a value corresponding to sopField in the row, row.sopObject==null; |
| // the following line sets deserialized sopObject into row.sopObject variable and sets sopField's value to null; |
| domainObject = concreteDescriptor.getSerializedObjectPolicy().getObjectFromRow(databaseRow, session, (ObjectLevelReadQuery)query); |
| } |
| if (domainObject == null) { |
| domainObject = concreteObjectBuilder.buildNewInstance(); |
| } |
| } |
| } |
| |
| // The object must be registered before building its attributes to resolve circular dependencies. |
| if (shouldMaintainCache && !shouldStoreBypassCache) { |
| if (domainWasMissing) { // may have build a new domain even though there is one in the cache |
| cacheKey.setObject(domainObject); |
| } |
| copyQueryInfoToCacheKey(cacheKey, query, databaseRow, session, concreteDescriptor); |
| } else if (cacheKey == null || (domainWasMissing && shouldRetrieveBypassCache)) { |
| cacheKey = new CacheKey(primaryKey); |
| cacheKey.setObject(domainObject); |
| } |
| concreteObjectBuilder.buildAttributesIntoObject(domainObject, cacheKey, databaseRow, query, joinManager, fetchGroup, false, session); |
| if (isProtected && (cacheKey != null)) { |
| cacheForeignKeyValues(databaseRow, cacheKey, session); |
| } |
| if (shouldMaintainCache && !shouldStoreBypassCache) { |
| // Set the fetch group to the domain object, after built. |
| if ((fetchGroup != null) && concreteDescriptor.hasFetchGroupManager()) { |
| EntityFetchGroup entityFetchGroup = concreteDescriptor.getFetchGroupManager().getEntityFetchGroup(fetchGroup); |
| if (entityFetchGroup !=null){ |
| entityFetchGroup.setOnEntity(domainObject, session); |
| } |
| } |
| } |
| // PERF: Cache the primary key and cache key if implements PersistenceEntity. |
| if (domainObject instanceof PersistenceEntity) { |
| updateCachedAttributes((PersistenceEntity) domainObject, cacheKey, primaryKey); |
| } |
| } else { |
| if (query.isReadObjectQuery() && ((ReadObjectQuery)query).shouldLoadResultIntoSelectionObject()) { |
| copyInto(domainObject, ((ReadObjectQuery)query).getSelectionObject()); |
| domainObject = ((ReadObjectQuery)query).getSelectionObject(); |
| } |
| |
| //check if the cached object has been invalidated |
| boolean isInvalidated = concreteDescriptor.getCacheInvalidationPolicy().isInvalidated(cacheKey, query.getExecutionTime()); |
| FetchGroupManager concreteFetchGroupManager = null; |
| if (concreteDescriptor.hasFetchGroupManager()) { |
| concreteFetchGroupManager = concreteDescriptor.getFetchGroupManager(); |
| } |
| |
| //CR #4365 - Queryid comparison used to prevent infinite recursion on refresh object cascade all |
| //if the concurrency manager is locked by the merge process then no refresh is required. |
| // bug # 3388383 If this thread does not have the active lock then someone is building the object so in order to maintain data integrity this thread will not |
| // fight to overwrite the object ( this also will avoid potential deadlock situations |
| if ((cacheKey.getActiveThread() == Thread.currentThread()) && ((query.shouldRefreshIdentityMapResult() || concreteDescriptor.shouldAlwaysRefreshCache() || isInvalidated ) && ((cacheKey.getLastUpdatedQueryId() != query.getQueryId()) && !cacheKey.isLockedByMergeManager()))) { |
| cacheHit = refreshObjectIfRequired(concreteDescriptor, cacheKey, cacheKey.getObject(), query, joinManager, databaseRow, session, false); |
| } else if ((concreteFetchGroupManager != null) && (concreteFetchGroupManager.isPartialObject(domainObject) && (!concreteFetchGroupManager.isObjectValidForFetchGroup(domainObject, concreteFetchGroupManager.getEntityFetchGroup(fetchGroup))))) { |
| cacheHit = false; |
| // The fetched object is not sufficient for the fetch group of the query |
| // refresh attributes of the query's fetch group. |
| concreteFetchGroupManager.unionEntityFetchGroupIntoObject(domainObject, concreteFetchGroupManager.getEntityFetchGroup(fetchGroup), session, false); |
| concreteObjectBuilder.buildAttributesIntoObject(domainObject, cacheKey, databaseRow, query, joinManager, fetchGroup, false, session); |
| if (cacheKey != null){ |
| cacheForeignKeyValues(databaseRow, cacheKey, session); |
| } |
| } |
| // 3655915: a query with join/batch'ing that gets a cache hit |
| // may require some attributes' valueholders to be re-built. |
| else if (joinManager != null && joinManager.hasJoinedAttributeExpressions()) { //some queries like ObjRel do not support joining |
| loadJoinedAttributes(concreteDescriptor, domainObject, cacheKey, databaseRow, joinManager, query, false); |
| } else if (query.isReadAllQuery() && ((ReadAllQuery)query).hasBatchReadAttributes()) { |
| loadBatchReadAttributes(concreteDescriptor, domainObject, cacheKey, databaseRow, query, joinManager, false); |
| } |
| } |
| } finally { |
| if (shouldMaintainCache && (cacheKey != null)) { |
| // bug 2681401: |
| // in case of exception (for instance, thrown by buildNewInstance()) |
| // cacheKey.getObject() may be null. |
| if (cacheKey.getObject() != null) { |
| cacheKey.updateAccess(); |
| } |
| |
| // PERF: Only use deferred locking if required. |
| if (query.requiresDeferredLocks()) { |
| cacheKey.releaseDeferredLock(); |
| } else { |
| cacheKey.release(); |
| } |
| } |
| } |
| if (!cacheHit) { |
| concreteObjectBuilder.instantiateEagerMappings(domainObject, session); |
| if (shouldMaintainCache && (cacheKey != null)) { |
| if (hasSopObject || (isSopQuery && this.hasCacheIndexesInSopObject)) { |
| // at least some of the cache index fields are missing from the row - extract index values from domainObject |
| concreteDescriptor.getCachePolicy().indexObjectInCache(cacheKey, domainObject, concreteDescriptor, session, !domainWasMissing); |
| } else { |
| concreteDescriptor.getCachePolicy().indexObjectInCache(cacheKey, databaseRow, domainObject, concreteDescriptor, session, !domainWasMissing); |
| } |
| } |
| } |
| if (query instanceof ObjectLevelReadQuery) { |
| LoadGroup group = 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<AbstractRecord> databaseRowsIn = new ArrayList(size); |
| for (int index = 0; index < size; index++) { |
| AbstractRecord databaseRow = (AbstractRecord)databaseRows.get(index); |
| // PERF: 1-m joining nulls out duplicate rows. |
| if (databaseRow != null) { |
| domainObjectsIn.add(buildObject(query, databaseRow, joinManager, session, this.descriptor, inheritancePolicy, |
| isUnitOfWork, shouldCacheQueryResults, shouldUseWrapperPolicy)); |
| databaseRowsIn.add(databaseRow); |
| } |
| } |
| policy.addAll(domainObjectsIn, domainObjects, session, databaseRowsIn, query, 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<AbstractRecord> databaseRowsIn = new ArrayList(); |
| for (Enumeration iterator = ((Vector)databaseRows).elements(); iterator.hasMoreElements(); ) { |
| AbstractRecord databaseRow = (AbstractRecord)iterator.nextElement(); |
| // PERF: 1-m joining nulls out duplicate rows. |
| if (databaseRow != null) { |
| domainObjectsIn.add(buildObject(query, databaseRow, joinManager, session, this.descriptor, inheritancePolicy, |
| isUnitOfWork, shouldCacheQueryResults, shouldUseWrapperPolicy)); |
| databaseRowsIn.add(databaseRow); |
| } |
| } |
| policy.addAll(domainObjectsIn, domainObjects, session, databaseRowsIn, query, 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<DatabaseField> primaryKeyFields = this.descriptor.getPrimaryKeyFields(); |
| |
| if (this.descriptor.getCachePolicy().getCacheKeyType() == CacheKeyType.ID_VALUE) { |
| return builder.getField(primaryKeyFields.get(0)).equal(primaryKey); |
| } |
| Expression expression = null; |
| int size = primaryKeyFields.size(); |
| Object[] primaryKeyValues = null; |
| if (primaryKey == null) { |
| primaryKeyValues = new Object[size]; |
| } else { |
| primaryKeyValues = ((CacheId)primaryKey).getPrimaryKey(); |
| } |
| for (int index = 0; index < size; index++) { |
| Object value = primaryKeyValues[index]; |
| DatabaseField field = primaryKeyFields.get(index); |
| if (value != null) { |
| Expression subExpression = builder.getField(field).equal(value); |
| expression = subExpression.and(expression); |
| } |
| } |
| |
| return expression; |
| } |
| |
| /** |
| * Build the primary key expression from the specified domain object. |
| */ |
| public Expression buildPrimaryKeyExpressionFromObject(Object domainObject, AbstractSession session) { |
| return buildPrimaryKeyExpressionFromKeys(extractPrimaryKeyFromObject(domainObject, session), session); |
| } |
| |
| /** |
| * Build the row representation of an object. |
| */ |
| public AbstractRecord buildRow(Object object, AbstractSession session, WriteType writeType) { |
| return buildRow(createRecord(session), object, session, writeType); |
| } |
| |
| /** |
| * Build the row representation of an object. |
| */ |
| public AbstractRecord buildRow(AbstractRecord databaseRow, Object object, AbstractSession session, WriteType writeType) { |
| // PERF: Avoid synchronized enumerator as is concurrency bottleneck. |
| List mappings = this.descriptor.getMappings(); |
| int mappingsSize = mappings.size(); |
| for (int index = 0; index < mappingsSize; index++) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); |
| mapping.writeFromObjectIntoRow(object, databaseRow, session, writeType); |
| } |
| |
| // If this descriptor is involved in inheritance add the class type. |
| if (this.descriptor.hasInheritance()) { |
| this.descriptor.getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow); |
| } |
| |
| // If this descriptor has multiple tables then we need to append the primary keys for |
| // the non default tables. |
| if (this.descriptor.hasMultipleTables() && !this.descriptor.isAggregateDescriptor()) { |
| addPrimaryKeyForNonDefaultTable(databaseRow, object, session); |
| } |
| |
| // If the session uses multi-tenancy, add the tenant id field. |
| if (getDescriptor().hasMultitenantPolicy()) { |
| getDescriptor().getMultitenantPolicy().addFieldsToRow(databaseRow, session); |
| } |
| |
| return databaseRow; |
| } |
| /** |
| * Build the row representation of the object for update. The row built does not |
| * contain entries for uninstantiated attributes. |
| */ |
| public AbstractRecord buildRowForShallowInsert(Object object, AbstractSession session) { |
| return buildRowForShallowInsert(createRecord(session), object, session); |
| } |
| |
| /** |
| * Build the row representation of the object for update. The row built does not |
| * contain entries for uninstantiated attributes. |
| */ |
| public AbstractRecord buildRowForShallowInsert(AbstractRecord databaseRow, Object object, AbstractSession session) { |
| // PERF: Avoid synchronized enumerator as is concurrency bottleneck. |
| List mappings = this.descriptor.getMappings(); |
| int mappingsSize = mappings.size(); |
| for (int index = 0; index < mappingsSize; index++) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); |
| mapping.writeFromObjectIntoRowForShallowInsert(object, databaseRow, session); |
| } |
| |
| // If this descriptor is involved in inheritance add the class type. |
| if (this.descriptor.hasInheritance()) { |
| this.descriptor.getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow); |
| } |
| |
| // If this descriptor has multiple tables then we need to append the primary keys for |
| // the non default tables. |
| if (!this.descriptor.isAggregateDescriptor()) { |
| addPrimaryKeyForNonDefaultTable(databaseRow, object, session); |
| } |
| |
| // If the session uses multi-tenancy, add the tenant id field. |
| if (getDescriptor().hasMultitenantPolicy()) { |
| getDescriptor().getMultitenantPolicy().addFieldsToRow(databaseRow, session); |
| } |
| |
| return databaseRow; |
| } |
| |
| /** |
| * Build the row representation of the object that contains only the fields nullified by shallow insert. |
| */ |
| public AbstractRecord buildRowForUpdateAfterShallowInsert(Object object, AbstractSession session, DatabaseTable table) { |
| return buildRowForUpdateAfterShallowInsert(createRecord(session), object, session, table); |
| } |
| |
| /** |
| * Build the row representation of the object that contains only the fields nullified by shallow insert. |
| */ |
| public AbstractRecord buildRowForUpdateAfterShallowInsert(AbstractRecord databaseRow, Object object, AbstractSession session, DatabaseTable table) { |
| for (DatabaseMapping mapping : this.descriptor.getMappings()) { |
| mapping.writeFromObjectIntoRowForUpdateAfterShallowInsert(object, databaseRow, session, table); |
| } |
| return databaseRow; |
| } |
| |
| /** |
| * Build the row representation of the object that contains only the fields nullified by shallow insert, with all values set to null. |
| */ |
| public AbstractRecord buildRowForUpdateBeforeShallowDelete(Object object, AbstractSession session, DatabaseTable table) { |
| return buildRowForUpdateBeforeShallowDelete(createRecord(session), object, session, table); |
| } |
| |
| /** |
| * Build the row representation of the object that contains only the fields nullified by shallow insert, with all values set to null. |
| */ |
| public AbstractRecord buildRowForUpdateBeforeShallowDelete(AbstractRecord databaseRow, Object object, AbstractSession session, DatabaseTable table) { |
| for (DatabaseMapping mapping : this.descriptor.getMappings()) { |
| mapping.writeFromObjectIntoRowForUpdateBeforeShallowDelete(object, databaseRow, session, table); |
| } |
| return databaseRow; |
| } |
| |
| /** |
| * Build the row representation of an object. |
| * This is only used for aggregates. |
| */ |
| public AbstractRecord buildRowWithChangeSet(AbstractRecord databaseRow, ObjectChangeSet objectChangeSet, AbstractSession session, WriteType writeType) { |
| List<ChangeRecord> changes = (List)objectChangeSet.getChanges(); |
| int size = changes.size(); |
| for (int index = 0; index < size; index++) { |
| ChangeRecord changeRecord = changes.get(index); |
| DatabaseMapping mapping = changeRecord.getMapping(); |
| mapping.writeFromObjectIntoRowWithChangeRecord(changeRecord, databaseRow, session, writeType); |
| } |
| |
| // If this descriptor is involved in inheritance add the class type. |
| if (this.descriptor.hasInheritance()) { |
| this.descriptor.getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow); |
| } |
| |
| // If the session uses multi-tenancy, add the tenant id field. |
| if (getDescriptor().hasMultitenantPolicy()) { |
| getDescriptor().getMultitenantPolicy().addFieldsToRow(databaseRow, session); |
| } |
| |
| return databaseRow; |
| } |
| |
| /** |
| * Build the row representation of an object. The row built is used only for translations |
| * for the expressions in the expression framework. |
| */ |
| public AbstractRecord buildRowForTranslation(Object object, AbstractSession session) { |
| AbstractRecord databaseRow = createRecord(session); |
| List<DatabaseMapping> primaryKeyMappings = getPrimaryKeyMappings(); |
| int size = primaryKeyMappings.size(); |
| for (int index = 0; index < size; index++) { |
| DatabaseMapping mapping = primaryKeyMappings.get(index); |
| if (mapping != null) { |
| mapping.writeFromObjectIntoRow(object, databaseRow, session, WriteType.UNDEFINED); |
| } |
| } |
| |
| // If this descriptor has multiple tables then we need to append the primary keys for |
| // the non default tables, this is require for m-m, dc defined in the Builder that prefixes the wrong table name. |
| // Ideally the mappings should take part in building the translation row so they can add required values. |
| if (this.descriptor.hasMultipleTables()) { |
| addPrimaryKeyForNonDefaultTable(databaseRow, object, session); |
| } |
| |
| return databaseRow; |
| } |
| |
| /** |
| * Build the row representation of the object for update. The row built does not |
| * contain entries for unchanged attributes. |
| */ |
| public AbstractRecord buildRowForUpdate(WriteObjectQuery query) { |
| AbstractRecord databaseRow = createRecord(query.getSession()); |
| return buildRowForUpdate(databaseRow, query); |
| } |
| |
| /** |
| * Build into the row representation of the object for update. The row does not |
| * contain entries for unchanged attributes. |
| */ |
| public AbstractRecord buildRowForUpdate(AbstractRecord databaseRow, WriteObjectQuery query) { |
| for (Iterator mappings = getNonPrimaryKeyMappings().iterator(); mappings.hasNext();) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.next(); |
| mapping.writeFromObjectIntoRowForUpdate(query, databaseRow); |
| } |
| |
| // If this descriptor is involved in inheritance and is an Aggregate, add the class type. |
| // Added Nov 8, 2000 Mostly by PWK but also JED |
| // Prs 24801 |
| // Modified Dec 11, 2000 TGW with assistance from PWK |
| // Prs 27554 |
| if (this.descriptor.hasInheritance() && this.descriptor.isAggregateDescriptor()) { |
| if (query.getObject() != null) { |
| if (query.getBackupClone() == null) { |
| this.descriptor.getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow); |
| } else { |
| if (!query.getObject().getClass().equals(query.getBackupClone().getClass())) { |
| this.descriptor.getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow); |
| } |
| } |
| } |
| } |
| |
| // If the session uses multi-tenancy, add the tenant id field. |
| if (getDescriptor().hasMultitenantPolicy()) { |
| getDescriptor().getMultitenantPolicy().addFieldsToRow(databaseRow, query.getExecutionSession()); |
| } |
| |
| return databaseRow; |
| } |
| |
| /** |
| * Build the row representation of the object for update. The row built does not |
| * contain entries for uninstantiated attributes. |
| */ |
| public AbstractRecord buildRowForUpdateWithChangeSet(WriteObjectQuery query) { |
| AbstractRecord databaseRow = createRecord(query.getSession()); |
| AbstractSession session = query.getSession(); |
| List changes = query.getObjectChangeSet().getChanges(); |
| int size = changes.size(); |
| for (int index = 0; index < size; index++) { |
| ChangeRecord changeRecord = (ChangeRecord)changes.get(index); |
| DatabaseMapping mapping = changeRecord.getMapping(); |
| mapping.writeFromObjectIntoRowWithChangeRecord(changeRecord, databaseRow, session, WriteType.UPDATE); |
| } |
| |
| return databaseRow; |
| } |
| |
| /** |
| * Build the row representation of an object. |
| */ |
| public AbstractRecord buildRowForWhereClause(ObjectLevelModifyQuery query) { |
| AbstractRecord databaseRow = createRecord(query.getSession()); |
| |
| // EL bug 319759 |
| if (query.isUpdateObjectQuery()) { |
| query.setShouldValidateUpdateCallCacheUse(true); |
| } |
| |
| for (Iterator mappings = this.descriptor.getMappings().iterator(); |
| mappings.hasNext();) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.next(); |
| mapping.writeFromObjectIntoRowForWhereClause(query, databaseRow); |
| } |
| |
| // If this descriptor has multiple tables then we need to append the primary keys for |
| // the non default tables. |
| if (!this.descriptor.isAggregateDescriptor()) { |
| addPrimaryKeyForNonDefaultTable(databaseRow); |
| } |
| |
| return databaseRow; |
| } |
| |
| /** |
| * Build the row from the primary key values. |
| */ |
| public AbstractRecord writeIntoRowFromPrimaryKeyValues(AbstractRecord row, Object primaryKey, AbstractSession session, boolean convert) { |
| List<DatabaseField> primaryKeyFields = this.descriptor.getPrimaryKeyFields(); |
| if (this.descriptor.getCachePolicy().getCacheKeyType() == CacheKeyType.ID_VALUE) { |
| DatabaseField field = primaryKeyFields.get(0); |
| Object value = primaryKey; |
| value = session.getPlatform(this.descriptor.getJavaClass()).getConversionManager().convertObject(value, field.getType()); |
| row.put(field, value); |
| return row; |
| } |
| int size = primaryKeyFields.size(); |
| Object[] primaryKeyValues = ((CacheId)primaryKey).getPrimaryKey(); |
| for (int index = 0; index < size; index++) { |
| DatabaseField field = primaryKeyFields.get(index); |
| Object value = primaryKeyValues[index]; |
| value = session.getPlatform(this.descriptor.getJavaClass()).getConversionManager().convertObject(value, field.getType()); |
| row.put(field, value); |
| } |
| |
| return row; |
| } |
| |
| /** |
| * Build the row from the primary key values. |
| */ |
| public AbstractRecord buildRowFromPrimaryKeyValues(Object key, AbstractSession session) { |
| AbstractRecord databaseRow = createRecord(this.descriptor.getPrimaryKeyFields().size(), session); |
| return writeIntoRowFromPrimaryKeyValues(databaseRow, key, session, true); |
| } |
| |
| /** |
| * Build the row of all of the fields used for insertion. |
| */ |
| public AbstractRecord buildTemplateInsertRow(AbstractSession session) { |
| AbstractRecord databaseRow = createRecord(session); |
| buildTemplateInsertRow(session, databaseRow); |
| return databaseRow; |
| } |
| |
| public void buildTemplateInsertRow(AbstractSession session, AbstractRecord databaseRow) { |
| for (Iterator mappings = this.descriptor.getMappings().iterator(); |
| mappings.hasNext();) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.next(); |
| mapping.writeInsertFieldsIntoRow(databaseRow, session); |
| } |
| |
| // If this descriptor is involved in inheritance add the class type. |
| if (this.descriptor.hasInheritance()) { |
| this.descriptor.getInheritancePolicy().addClassIndicatorFieldToInsertRow(databaseRow); |
| } |
| |
| // If this descriptor has multiple tables then we need to append the primary keys for |
| // the non default tables. |
| if (!this.descriptor.isAggregateDescriptor()) { |
| addPrimaryKeyForNonDefaultTable(databaseRow); |
| } |
| |
| if (this.descriptor.usesOptimisticLocking()) { |
| this.descriptor.getOptimisticLockingPolicy().addLockFieldsToUpdateRow(databaseRow, session); |
| } |
| |
| // If the session uses multi-tenancy, add the tenant id field. |
| if (this.descriptor.hasMultitenantPolicy()) { |
| this.descriptor.getMultitenantPolicy().addFieldsToRow(databaseRow, session); |
| } |
| |
| if (this.descriptor.hasSerializedObjectPolicy()) { |
| databaseRow.put(this.descriptor.getSerializedObjectPolicy().getField(), null); |
| } |
| |
| // remove any fields from the databaseRow |
| trimFieldsForInsert(session, databaseRow); |
| } |
| |
| /** |
| * INTERNAL |
| * Remove a potential sequence number field and invoke the ReturningPolicy trimModifyRowsForInsert method |
| */ |
| public void trimFieldsForInsert(AbstractSession session, AbstractRecord databaseRow) { |
| ClassDescriptor descriptor = this.descriptor; |
| if (descriptor.usesSequenceNumbers() && descriptor.getSequence().shouldAcquireValueAfterInsert()) { |
| databaseRow.remove(descriptor.getSequenceNumberField()); |
| } |
| if (descriptor.hasReturningPolicy()) { |
| descriptor.getReturningPolicy().trimModifyRowForInsert(databaseRow); |
| } |
| } |
| |
| /** |
| * Build the row representation of the object for update. The row built does not |
| * contain entries for uninstantiated attributes. |
| */ |
| public AbstractRecord buildTemplateUpdateRow(AbstractSession session) { |
| AbstractRecord databaseRow = createRecord(session); |
| |
| for (Iterator mappings = getNonPrimaryKeyMappings().iterator(); |
| mappings.hasNext();) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.next(); |
| mapping.writeUpdateFieldsIntoRow(databaseRow, session); |
| } |
| |
| if (this.descriptor.usesOptimisticLocking()) { |
| this.descriptor.getOptimisticLockingPolicy().addLockFieldsToUpdateRow(databaseRow, session); |
| } |
| |
| if (this.descriptor.hasSerializedObjectPolicy()) { |
| databaseRow.put(this.descriptor.getSerializedObjectPolicy().getField(), null); |
| } |
| |
| return databaseRow; |
| } |
| |
| /** |
| * Build and return the expression to use as the where clause to an update object. |
| * The row is passed to allow the version number to be extracted from it. |
| */ |
| public Expression buildUpdateExpression(DatabaseTable table, AbstractRecord transactionRow, AbstractRecord modifyRow) { |
| // Only the first table must use the lock check. |
| Expression primaryKeyExpression = buildPrimaryKeyExpression(table); |
| if (this.descriptor.usesOptimisticLocking()) { |
| return this.descriptor.getOptimisticLockingPolicy().buildUpdateExpression(table, primaryKeyExpression, transactionRow, modifyRow); |
| } else { |
| return primaryKeyExpression; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Build just the primary key mappings into the object. |
| */ |
| public void buildPrimaryKeyAttributesIntoObject(Object original, AbstractRecord databaseRow, ObjectBuildingQuery query, AbstractSession session) throws DatabaseException, QueryException { |
| // PERF: Avoid synchronized enumerator as is concurrency bottleneck. |
| List mappings = this.primaryKeyMappings; |
| int mappingsSize = mappings.size(); |
| for (int i = 0; i < mappingsSize; i++) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.get(i); |
| mapping.buildShallowOriginalFromRow(databaseRow, original, null, query, session); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * For reading through the write connection when in transaction, |
| * We need a partially populated original, so that we |
| * can build a clone using the copy policy, even though we can't |
| * put this original in the shared cache yet; just build a |
| * shallow original (i.e. just enough to copy over the primary |
| * key and some direct attributes) and keep it on the UOW. |
| */ |
| public void buildAttributesIntoShallowObject(Object original, AbstractRecord databaseRow, ObjectBuildingQuery query) throws DatabaseException, QueryException { |
| AbstractSession executionSession = query.getSession().getExecutionSession(query); |
| |
| // PERF: Avoid synchronized enumerator as is concurrency bottleneck. |
| List pkMappings = getPrimaryKeyMappings(); |
| int mappingsSize = pkMappings.size(); |
| for (int i = 0; i < mappingsSize; i++) { |
| DatabaseMapping mapping = (DatabaseMapping)pkMappings.get(i); |
| |
| //if (query.shouldReadMapping(mapping)) { |
| if (!mapping.isAbstractColumnMapping()) { |
| mapping.buildShallowOriginalFromRow(databaseRow, original, null, query, executionSession); |
| } |
| } |
| List mappings = this.descriptor.getMappings(); |
| mappingsSize = mappings.size(); |
| for (int i = 0; i < mappingsSize; i++) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.get(i); |
| |
| //if (query.shouldReadMapping(mapping)) { |
| if (mapping.isAbstractColumnMapping()) { |
| mapping.buildShallowOriginalFromRow(databaseRow, original, null, query, executionSession); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * For reading through the write connection when in transaction, |
| * populate the clone directly from the database row. |
| */ |
| public void buildAttributesIntoWorkingCopyClone(Object clone, CacheKey sharedCacheKey, ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, boolean forRefresh) throws DatabaseException, QueryException { |
| if (this.descriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy()) { |
| if (buildAttributesIntoWorkingCopyCloneSOP(clone, sharedCacheKey, query, joinManager, databaseRow, unitOfWork, forRefresh)) { |
| return; |
| } |
| } |
| // PERF: Cache if all mappings should be read. |
| boolean readAllMappings = query.shouldReadAllMappings(); |
| List mappings = this.descriptor.getMappings(); |
| int size = mappings.size(); |
| FetchGroup executionFetchGroup = query.getExecutionFetchGroup(this.descriptor); |
| for (int index = 0; index < size; index++) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); |
| if (readAllMappings || query.shouldReadMapping(mapping, executionFetchGroup)) { |
| mapping.buildCloneFromRow(databaseRow, joinManager, clone, sharedCacheKey, query, unitOfWork, unitOfWork); |
| } |
| } |
| |
| // PERF: Avoid events if no listeners. |
| if (this.descriptor.getEventManager().hasAnyEventListeners()) { |
| postBuildAttributesIntoWorkingCopyCloneEvent(clone, databaseRow, query, unitOfWork, forRefresh); |
| } |
| } |
| |
| /** |
| * For reading through the write connection when in transaction, |
| * populate the clone directly from the database row. |
| * Should not be called unless (this.descriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy()) |
| * This method populates the object only in if some mappings potentially should be read using sopObject and other mappings - not using it. |
| * That happens when the row has been just read from the database and potentially has serialized object still in deserialized bits as a field value. |
| * Note that clone == sopObject is the same case, but (because clone has to be set into cache beforehand) extraction of sopObject |
| * from bit was done right before this method is called. |
| * If attempt to deserialize sopObject from bits has failed, but SOP was setup to allow recovery |
| * (all mapped all fields/value mapped to the object were read, not just those excluded from SOP) |
| * then fall through to buildAttributesIntoWorkingCopyClone. |
| * Nothing should be done if sopObject is not null, but clone != sopObject: |
| * the only way to get into this case should be with original query not maintaining cache, |
| * through a back reference to the original object, which is already being built (or has been built). |
| * @return whether the object has been populated with attributes, if not then buildAttributesIntoWorkingCopyClone should be called. |
| */ |
| protected boolean buildAttributesIntoWorkingCopyCloneSOP(Object clone, CacheKey sharedCacheKey, ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, boolean forRefresh) throws DatabaseException { |
| Object sopObject = databaseRow.getSopObject(); |
| if (clone == sopObject) { |
| // clone is sopObject |
| // PERF: Cache if all mappings should be read. |
| boolean readAllMappings = query.shouldReadAllMappings(); |
| FetchGroup executionFetchGroup = query.getExecutionFetchGroup(this.descriptor); |
| for (DatabaseMapping mapping : this.descriptor.getMappings()) { |
| if (readAllMappings || query.shouldReadMapping(mapping, executionFetchGroup)) { |
| // to avoid re-setting the same attribute value to domainObject |
| // only populate if either mapping (possibly nested) may reference entity or mapping does not use sopObject |
| if (mapping.hasNestedIdentityReference() || mapping.isOutOnlySopObject()) { |
| if (mapping.isOutSopObject()) { |
| // the mapping should be processed as if there is no sopObject |
| databaseRow.setSopObject(null); |
| mapping.buildCloneFromRow(databaseRow, joinManager, clone, sharedCacheKey, query, unitOfWork, unitOfWork); |
| } else { |
| databaseRow.setSopObject(sopObject); |
| mapping.buildCloneFromRow(databaseRow, joinManager, clone, sharedCacheKey, query, unitOfWork, unitOfWork); |
| } |
| } |
| } |
| } |
| // PERF: Avoid events if no listeners. |
| if (this.descriptor.hasEventManager()) { |
| postBuildAttributesIntoWorkingCopyCloneEvent(clone, databaseRow, query, unitOfWork, forRefresh); |
| } |
| // sopObject has been processed by all relevant mappings, no longer required. |
| databaseRow.setSopObject(null); |
| return true; |
| } else { |
| if (sopObject == null) { |
| // serialized sopObject is a value corresponding to sopField in the row, row.sopObject==null; |
| // the following line sets deserialized sopObject into row.sopObject variable and sets sopField's value to null; |
| sopObject = this.descriptor.getSerializedObjectPolicy().getObjectFromRow(databaseRow, unitOfWork, (ObjectLevelReadQuery)query); |
| if (sopObject != null) { |
| // PERF: Cache if all mappings should be read. |
| boolean readAllMappings = query.shouldReadAllMappings(); |
| FetchGroup executionFetchGroup = query.getExecutionFetchGroup(this.descriptor); |
| for (DatabaseMapping mapping : this.descriptor.getMappings()) { |
| if (readAllMappings || query.shouldReadMapping(mapping, executionFetchGroup)) { |
| if (mapping.isOutSopObject()) { |
| // the mapping should be processed as if there is no sopObject |
| databaseRow.setSopObject(null); |
| mapping.buildCloneFromRow(databaseRow, joinManager, clone, sharedCacheKey, query, unitOfWork, unitOfWork); |
| } else { |
| databaseRow.setSopObject(sopObject); |
| mapping.buildCloneFromRow(databaseRow, joinManager, clone, sharedCacheKey, query, unitOfWork, unitOfWork); |
| } |
| } |
| } |
| // PERF: Avoid events if no listeners. |
| if (this.descriptor.hasEventManager()) { |
| postBuildAttributesIntoWorkingCopyCloneEvent(clone, databaseRow, query, unitOfWork, forRefresh); |
| } |
| // sopObject has been processed by all relevant mappings, no longer required. |
| databaseRow.setSopObject(null); |
| return true; |
| } else { |
| // SOP failed to create sopObject, but exception hasn't been thrown. |
| // That means recovery is possible - fall through to to buildAttributesIntoWorkingCopyClone |
| return false; |
| } |
| } else { |
| // A mapping under SOP can't have another SOP on its reference descriptor, |
| // but that's what seem to be happening. |
| // The only way to get here should be with original query not maintaining cache, |
| // through a back reference to the original object, which is already being built (or has been built). |
| // Leave without building. |
| return true; |
| } |
| } |
| } |
| |
| protected void postBuildAttributesIntoWorkingCopyCloneEvent(Object clone, AbstractRecord databaseRow, ObjectBuildingQuery query, UnitOfWorkImpl unitOfWork, boolean forRefresh) { |
| // Need to run post build or refresh selector, currently check with the query for this, |
| // I'm not sure which should be called it case of refresh building a new object, currently refresh is used... |
| DescriptorEvent event = new DescriptorEvent(clone); |
| event.setQuery(query); |
| event.setSession(unitOfWork); |
| event.setDescriptor(this.descriptor); |
| event.setRecord(databaseRow); |
| if (forRefresh) { |
| event.setEventCode(DescriptorEventManager.PostRefreshEvent); |
| } else { |
| event.setEventCode(DescriptorEventManager.PostBuildEvent); |
| //fire a postBuildEvent then the postCloneEvent |
| unitOfWork.deferEvent(event); |
| |
| event = new DescriptorEvent(clone); |
| event.setQuery(query); |
| event.setSession(unitOfWork); |
| event.setDescriptor(this.descriptor); |
| event.setRecord(databaseRow); |
| //bug 259404: ensure postClone is called for objects built directly into the UnitOfWork |
| //in this case, the original is the clone |
| event.setOriginalObject(clone); |
| event.setEventCode(DescriptorEventManager.PostCloneEvent); |
| } |
| unitOfWork.deferEvent(event); |
| } |
| |
| /** |
| * INTERNAL: |
| * Builds a working copy clone directly from the database row. |
| * This is the key method that allows us to execute queries against a |
| * UnitOfWork while in transaction and not cache the results in the shared |
| * cache. This is because we might violate transaction isolation by |
| * putting uncommitted versions of objects in the shared cache. |
| */ |
| protected Object buildWorkingCopyCloneFromRow(ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, Object primaryKey, CacheKey preFetchedCacheKey) throws DatabaseException, QueryException { |
| ClassDescriptor descriptor = this.descriptor; |
| |
| // If the clone already exists then it may only need to be refreshed or returned. |
| // We call directly on the identity map to avoid going to the parent, |
| // registering if found, and wrapping the result. |
| // Acquire or create the cache key as is need once the object is build anyway. |
| CacheKey unitOfWorkCacheKey = unitOfWork.getIdentityMapAccessorInstance().getIdentityMapManager().acquireLock(primaryKey, descriptor.getJavaClass(), false, descriptor, true); |
| Object workingClone = unitOfWorkCacheKey.getObject(); |
| FetchGroup fetchGroup = query.getExecutionFetchGroup(descriptor); |
| FetchGroupManager fetchGroupManager = descriptor.getFetchGroupManager(); |
| try { |
| // If there is a clone, and it is not a refresh then just return it. |
| boolean wasAClone = workingClone != null; |
| boolean isARefresh = query.shouldRefreshIdentityMapResult() || (query.isLockQuery() && (!wasAClone || !query.isClonePessimisticLocked(workingClone, unitOfWork))); |
| // Also need to refresh if the clone is a partial object and query requires more than its fetch group. |
| if (wasAClone && fetchGroupManager != null && (fetchGroupManager.isPartialObject(workingClone) && (!fetchGroupManager.isObjectValidForFetchGroup(workingClone, fetchGroupManager.getEntityFetchGroup(fetchGroup))))) { |
| isARefresh = true; |
| } |
| if (wasAClone && (!isARefresh)) { |
| return workingClone; |
| } |
| |
| boolean wasAnOriginal = false; |
| boolean isIsolated = descriptor.getCachePolicy().shouldIsolateObjectsInUnitOfWork() |
| || (descriptor.shouldIsolateObjectsInUnitOfWorkEarlyTransaction() && unitOfWork.wasTransactionBegunPrematurely()); |
| |
| Object original = null; |
| CacheKey originalCacheKey = null; |
| // If not refreshing can get the object from the cache. |
| if ((!isARefresh) && (!isIsolated) && !query.shouldRetrieveBypassCache() && !unitOfWork.shouldReadFromDB() && (!unitOfWork.shouldForceReadFromDB(query, primaryKey))) { |
| AbstractSession session = unitOfWork.getParentIdentityMapSession(query); |
| if (preFetchedCacheKey == null){ |
| originalCacheKey = session.getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, descriptor.getJavaClass(), descriptor, false); |
| }else{ |
| originalCacheKey = preFetchedCacheKey; |
| originalCacheKey.acquireLock(query); |
| } |
| if (originalCacheKey != null) { |
| // PERF: Read-lock is not required on object as unit of work will acquire this on clone and object cannot gc and object identity is maintained. |
| original = originalCacheKey.getObject(); |
| wasAnOriginal = original != null; |
| // If the original is invalid or always refresh then need to refresh. |
| isARefresh = wasAnOriginal && (descriptor.shouldAlwaysRefreshCache() || descriptor.getCacheInvalidationPolicy().isInvalidated(originalCacheKey, query.getExecutionTime())); |
| // Otherwise can just register the cached original object and return it. |
| if (wasAnOriginal && (!isARefresh)){ |
| if (descriptor.getCachePolicy().isSharedIsolation() || !descriptor.shouldIsolateProtectedObjectsInUnitOfWork()) { |
| // using shared isolation and the original is from the shared cache |
| // or using protected isolation and isolated client sessions |
| return unitOfWork.cloneAndRegisterObject(original, originalCacheKey, unitOfWorkCacheKey, descriptor); |
| } |
| } |
| } |
| } |
| |
| if (!wasAClone) { |
| // This code is copied from UnitOfWork.cloneAndRegisterObject. Unlike |
| // that method we don't need to lock the shared cache, because |
| // are not building off of an original in the shared cache. |
| // The copy policy is easier to invoke if we have an original. |
| if (wasAnOriginal && !query.shouldRetrieveBypassCache()) { |
| workingClone = instantiateWorkingCopyClone(original, unitOfWork); |
| // intentionally put nothing in clones to originals, unless really was one. |
| unitOfWork.getCloneToOriginals().put(workingClone, original); |
| } else { |
| if (descriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy() && !databaseRow.hasSopObject()) { |
| // serialized sopObject is a value corresponding to sopField in the row, row.sopObject==null; |
| // the following line sets deserialized sopObject into row.sopObject variable and sets sopField's value to null; |
| workingClone = descriptor.getSerializedObjectPolicy().getObjectFromRow(databaseRow, unitOfWork, (ObjectLevelReadQuery)query); |
| } |
| if (workingClone == null) { |
| // What happens if a copy policy is defined is not pleasant. |
| //workingClone = instantiateWorkingCopyCloneFromRow(databaseRow, query, primaryKey, unitOfWork); |
| // Create a new instance instead. The object is populated later by buildAttributesIntoWorkingCopyClone method. |
| workingClone = buildNewInstance(); |
| } |
| } |
| |
| // This must be registered before it is built to avoid cycles. |
| // The version and read is set below in copyQueryInfoToCacheKey. |
| unitOfWorkCacheKey.setObject(workingClone); |
| |
| // This must be registered before it is built to avoid cycles. |
| unitOfWork.getCloneMapping().put(workingClone, workingClone); |
| } |
| |
| // Must avoid infinite loops while refreshing. |
| if (wasAClone && (unitOfWorkCacheKey.getLastUpdatedQueryId() >= query.getQueryId())) { |
| return workingClone; |
| } |
| copyQueryInfoToCacheKey(unitOfWorkCacheKey, query, databaseRow, unitOfWork, descriptor); |
| |
| ObjectChangePolicy policy = descriptor.getObjectChangePolicy(); |
| // If it was a clone the change listener must be cleared after. |
| if (!wasAClone) { |
| // The change listener must be set before building the clone as aggregate/collections need the listener. |
| policy.setChangeListener(workingClone, unitOfWork, descriptor); |
| } |
| |
| // Turn it 'off' to prevent unwanted events. |
| policy.dissableEventProcessing(workingClone); |
| if (isARefresh && fetchGroupManager != null) { |
| fetchGroupManager.setObjectFetchGroup(workingClone, query.getExecutionFetchGroup(this.descriptor), unitOfWork); |
| } |
| if (!unitOfWork.wasTransactionBegunPrematurely() && descriptor.getCachePolicy().isProtectedIsolation() && !isIsolated && !query.shouldStoreBypassCache()) { |
| // we are at this point because we have isolated protected entities to the UnitOfWork |
| // we should ensure that we populate the cache as well. |
| originalCacheKey = (CacheKey) buildObject(true, query, databaseRow, unitOfWork.getParentIdentityMapSession(descriptor, false, true), primaryKey, preFetchedCacheKey, descriptor, joinManager); |
| } |
| //If we are unable to access the shared cache because of any of the above settings at this point |
| // the cachekey will be null so the attribute building will not be able to access the shared cache. |
| if (isARefresh){ |
| //if we need to refresh the UOW then remove the cache key and the clone will be rebuilt not using any of the |
| //cache. This should be updated to force the buildAttributesIntoWorkingCopyClone to refresh the objects |
| originalCacheKey = null; |
| } |
| // Build/refresh the clone from the row. |
| buildAttributesIntoWorkingCopyClone(workingClone, originalCacheKey, query, joinManager, databaseRow, unitOfWork, wasAClone); |
| // Set fetch group after building object if not a refresh to avoid checking fetch during building. |
| if ((!isARefresh) && fetchGroupManager != null) { |
| 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<DatabaseMapping> mappings = this.relationshipMappings; |
| for (int index = 0; index < mappings.size(); index++) { |
| DatabaseMapping mapping = mappings.get(index); |
| mapping.cascadePerformRemoveIfRequired(object, uow, visitedObjects); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to iterate over the specified object's mappings and cascade |
| * remove orphaned private owned objects from the UnitOfWorkChangeSet and IdentityMap. |
| */ |
| public void cascadePerformRemovePrivateOwnedObjectFromChangeSet(Object object, UnitOfWorkImpl uow, Map visitedObjects) { |
| if (object != null && !this.isSimple) { |
| for (DatabaseMapping mapping : this.relationshipMappings) { |
| // only cascade into private owned mappings |
| if (mapping.isPrivateOwned()) { |
| mapping.cascadePerformRemovePrivateOwnedObjectFromChangeSetIfRequired(object, uow, visitedObjects); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to store the FK values used for this mapping in the cachekey. |
| * This is used when the mapping is protected but we have retrieved the fk values and will cache |
| * them for use when the entity is cloned. |
| */ |
| public void cacheForeignKeyValues(AbstractRecord databaseRecord, CacheKey cacheKey, AbstractSession session) { |
| Set<DatabaseField> foreignKeys = this.descriptor.getForeignKeyValuesForCaching(); |
| if (foreignKeys.isEmpty()) { |
| return; |
| } |
| DatabaseRecord cacheRecord = new DatabaseRecord(foreignKeys.size()); |
| for (DatabaseField field : foreignKeys) { |
| cacheRecord.put(field, databaseRecord.get(field)); |
| } |
| cacheKey.setProtectedForeignKeys(cacheRecord); |
| |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to store the FK values used for this mapping in the cachekey. |
| * This is used when the mapping is protected but we have retrieved the fk values and will cache |
| * them for use when the entity is cloned. |
| */ |
| public void cacheForeignKeyValues(Object source, CacheKey cacheKey, ClassDescriptor descriptor, AbstractSession session) { |
| Set<DatabaseField> foreignKeys = this.descriptor.getForeignKeyValuesForCaching(); |
| if (foreignKeys.isEmpty()) { |
| return; |
| } |
| DatabaseRecord cacheRecord = new DatabaseRecord(foreignKeys.size()); |
| for (DatabaseField field : foreignKeys) { |
| cacheRecord.put(field, extractValueFromObjectForField(source, field, session)); |
| } |
| cacheKey.setProtectedForeignKeys(cacheRecord); |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade discover and persist new objects during commit. |
| * It may raise exceptions as described in the EJB3 specification |
| */ |
| public void cascadeDiscoverAndPersistUnregisteredNewObjects(Object object, Map newObjects, Map unregisteredExistingObjects, Map visitedObjects, UnitOfWorkImpl uow, Set cascadeErrors) { |
| // PERF: Only process relationships. |
| if (!this.isSimple) { |
| List<DatabaseMapping> mappings = this.relationshipMappings; |
| int size = mappings.size(); |
| FetchGroupManager fetchGroupManager = descriptor.getFetchGroupManager(); |
| // Only cascade fetched mappings. |
| if ((fetchGroupManager != null) && fetchGroupManager.isPartialObject(object)) { |
| for (int index = 0; index < size; index++) { |
| DatabaseMapping mapping = mappings.get(index); |
| if (fetchGroupManager.isAttributeFetched(object, mapping.getAttributeName())) { |
| mapping.cascadeDiscoverAndPersistUnregisteredNewObjects(object, newObjects, unregisteredExistingObjects, visitedObjects, uow, cascadeErrors); |
| } |
| } |
| } else { |
| for (int index = 0; index < size; index++) { |
| DatabaseMapping mapping = mappings.get(index); |
| mapping.cascadeDiscoverAndPersistUnregisteredNewObjects(object, newObjects, unregisteredExistingObjects, visitedObjects, uow, cascadeErrors); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used by the UnitOfWork to cascade registration of new objects. |
| * It may raise exceptions as described in the EJB3 specification |
| */ |
| public void cascadeRegisterNewForCreate(Object object, UnitOfWorkImpl uow, Map visitedObjects) { |
| // PERF: Only process relationships. |
| if (!this.isSimple) { |
| List<DatabaseMapping> mappings = this.relationshipMappings; |
| int size = mappings.size(); |
| FetchGroupManager fetchGroupManager = this.descriptor.getFetchGroupManager(); |
| // Only cascade fetched mappings. |
| if ((fetchGroupManager != null) && fetchGroupManager.isPartialObject(object)) { |
| for (int index = 0; index < size; index++) { |
| DatabaseMapping mapping = mappings.get(index); |
| if (fetchGroupManager.isAttributeFetched(object, mapping.getAttributeName())) { |
| mapping.cascadeRegisterNewIfRequired(object, uow, visitedObjects); |
| } |
| } |
| } else { |
| for (int index = 0; index < size; index++) { |
| DatabaseMapping mapping = mappings.get(index); |
| mapping.cascadeRegisterNewIfRequired(object, uow, visitedObjects); |
| } |
| } |
| } |
| // Allow persist to set the partitioning connection. |
| if (this.descriptor.getPartitioningPolicy() != null) { |
| this.descriptor.getPartitioningPolicy().partitionPersist(uow.getParent(), object, this.descriptor); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method creates a records change set for a particular object. |
| * It should only be used by aggregates. |
| * @return ObjectChangeSet |
| */ |
| public ObjectChangeSet compareForChange(Object clone, Object backUp, UnitOfWorkChangeSet changeSet, AbstractSession session) { |
| // delegate the change comparison to this objects ObjectChangePolicy - TGW |
| return descriptor.getObjectChangePolicy().calculateChanges(clone, backUp, backUp == null, changeSet, ((UnitOfWorkImpl)session), this.descriptor, true); |
| } |
| |
| /** |
| * Compares the two specified objects |
| */ |
| public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) { |
| // PERF: Avoid iterator. |
| List mappings = this.descriptor.getMappings(); |
| for (int index = 0; index < mappings.size(); index++) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); |
| |
| if (!mapping.compareObjects(firstObject, secondObject, session)) { |
| Object firstValue = mapping.getAttributeValueFromObject(firstObject); |
| Object secondValue = mapping.getAttributeValueFromObject(secondObject); |
| session.log(SessionLog.FINEST, SessionLog.QUERY, "compare_failed", mapping, firstValue, secondValue); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Copy each attribute from one object into the other. |
| */ |
| public void copyInto(Object source, Object target, boolean cloneOneToOneValueHolders) { |
| // PERF: Avoid iterator. |
| List mappings = this.descriptor.getMappings(); |
| for (int index = 0; index < mappings.size(); index++) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); |
| Object value = null; |
| if (cloneOneToOneValueHolders && mapping.isForeignReferenceMapping()){ |
| value = ((ForeignReferenceMapping)mapping).getAttributeValueWithClonedValueHolders(source); |
| } else { |
| value = mapping.getAttributeValueFromObject(source); |
| } |
| mapping.setAttributeValueInObject(target, value); |
| } |
| } |
| |
| /** |
| * Copy each attribute from one object into the other. |
| */ |
| public void copyInto(Object source, Object target) { |
| copyInto(source, target, false); |
| } |
| |
| /** |
| * Return a copy of the object. |
| * This is NOT used for unit of work but for templatizing an object. |
| * The depth and primary key reseting are passed in. |
| */ |
| public Object copyObject(Object original, CopyGroup copyGroup) { |
| Object copy = copyGroup.getCopies().get(original); |
| if (copyGroup.shouldCascadeTree()) { |
| FetchGroupManager fetchGroupManager = this.descriptor.getFetchGroupManager(); |
| if (fetchGroupManager != null) { |
| // empty copy group means all the attributes should be copied - don't alter it. |
| if (copyGroup.hasItems()) { |
| // by default add primary key attribute(s) if not already in the group |
| if (!copyGroup.shouldResetPrimaryKey()) { |
| for (DatabaseMapping mapping : this.primaryKeyMappings) { |
| String name = mapping.getAttributeName(); |
| if (!copyGroup.containsAttributeInternal(name)) { |
| copyGroup.addAttribute(name); |
| } |
| } |
| } else { |
| for (DatabaseMapping mapping : this.primaryKeyMappings) { |
| if (mapping.isForeignReferenceMapping()) { |
| String name = mapping.getAttributeName(); |
| if (!copyGroup.containsAttributeInternal(name)) { |
| copyGroup.addAttribute(name); |
| } |
| } |
| } |
| } |
| |
| // by default version attribute if not already in the group |
| if (!copyGroup.shouldResetVersion()) { |
| if (this.lockAttribute != null) { |
| if (!copyGroup.containsAttributeInternal(this.lockAttribute)) { |
| copyGroup.addAttribute(this.lockAttribute); |
| } |
| } |
| } |
| |
| FetchGroup fetchGroup = fetchGroupManager.getObjectFetchGroup(original); |
| if (fetchGroup != null) { |
| if (!fetchGroup.getAttributeNames().containsAll(copyGroup.getAttributeNames())) { |
| // trigger fetch group if it does not contain all attributes of the copy group. |
| fetchGroup.onUnfetchedAttribute((FetchGroupTracker)original, null); |
| } |
| } |
| } |
| |
| // Entity fetch group currently set on copyObject |
| EntityFetchGroup existingEntityFetchGroup = null; |
| if (copy != null) { |
| Object[] copyArray = (Object[])copy; |
| // copy of the original |
| copy = copyArray[0]; |
| // A set of CopyGroups that have visited. |
| Set<CopyGroup> visitedCopyGroups = (Set<CopyGroup>)copyArray[1]; |
| |
| if(visitedCopyGroups.contains(copyGroup)) { |
| // original has been already visited with this copyGroup - leave |
| return copy; |
| } else { |
| visitedCopyGroups.add(copyGroup); |
| } |
| |
| existingEntityFetchGroup = fetchGroupManager.getObjectEntityFetchGroup(copy); |
| } |
| |
| // Entity fetch group that will be assigned to copyObject |
| EntityFetchGroup newEntityFetchGroup = null; |
| |
| // Attributes to be visited - only reference mappings will be visited. |
| // If null then all attributes should be visited. |
| Set<String> attributesToVisit = copyGroup.getAttributeNames(); |
| // Attributes to be copied |
| Set<String> attributesToCopy = attributesToVisit; |
| boolean shouldCopyAllAttributes = false; |
| boolean shouldAssignNewEntityFetchGroup = false; |
| if(copy != null && existingEntityFetchGroup == null) { |
| // all attributes have been already copied |
| attributesToCopy = null; |
| } else { |
| // Entity fetch group corresponding to copyPolicy. |
| // Note that empty, or null, or containing all arguments attributesToCopy |
| // results in copyGroupFetchGroup = null; |
| EntityFetchGroup copyGroupEntityFetchGroup = fetchGroupManager.getEntityFetchGroup(attributesToCopy); |
| if(copyGroupEntityFetchGroup == null) { |
| // all attributes will be copied |
| shouldCopyAllAttributes = true; |
| } |
| |
| if(copy != null) { |
| if(copyGroupEntityFetchGroup != null) { |
| if(!copyGroup.shouldResetPrimaryKey()) { |
| if(!existingEntityFetchGroup.getAttributeNames().containsAll(attributesToCopy)) { |
| // Entity fetch group that will be assigned to copy object |
| newEntityFetchGroup = fetchGroupManager.flatUnionFetchGroups(existingEntityFetchGroup, copyGroupEntityFetchGroup, false); |
| shouldAssignNewEntityFetchGroup = true; |
| } |
| } |
| attributesToCopy = new HashSet(attributesToCopy); |
| attributesToCopy.removeAll(existingEntityFetchGroup.getAttributeNames()); |
| } |
| } else { |
| // copy does not exist - create it |
| copy = copyGroup.getSession().getDescriptor(original).getObjectBuilder().buildNewInstance(); |
| Set<CopyGroup> visitedCopyGroups = new HashSet(); |
| visitedCopyGroups.add(copyGroup); |
| copyGroup.getCopies().put(original, new Object[]{copy, visitedCopyGroups}); |
| if(!copyGroup.shouldResetPrimaryKey()) { |
| newEntityFetchGroup = copyGroupEntityFetchGroup; |
| shouldAssignNewEntityFetchGroup = true; |
| } |
| } |
| } |
| if(shouldAssignNewEntityFetchGroup) { |
| fetchGroupManager.setObjectFetchGroup(copy, newEntityFetchGroup, null); |
| } |
| |
| for (DatabaseMapping mapping : getDescriptor().getMappings()) { |
| String name = mapping.getAttributeName(); |
| boolean shouldCopy = shouldCopyAllAttributes || (attributesToCopy != null && attributesToCopy.contains(name)); |
| boolean shouldVisit = attributesToVisit == null || attributesToVisit.contains(name); |
| if(shouldCopy || shouldVisit) { |
| boolean isVisiting = false; |
| // unless it's a reference mapping pass copyGroup - just to carry the session. |
| CopyGroup mappingCopyGroup = copyGroup; |
| if(mapping.isForeignReferenceMapping()) { |
| ForeignReferenceMapping frMapping = (ForeignReferenceMapping)mapping; |
| ClassDescriptor referenceDescriptor = frMapping.getReferenceDescriptor(); |
| if(referenceDescriptor != null) { |
| isVisiting = true; |
| mappingCopyGroup = copyGroup.getGroup(name); |
| if(mappingCopyGroup == null) { |
| FetchGroupManager referenceFetchGroupManager = referenceDescriptor.getFetchGroupManager(); |
| if(referenceFetchGroupManager != null) { |
| EntityFetchGroup nonReferenceEntityFetchGroup = referenceFetchGroupManager.getNonReferenceEntityFetchGroup(copyGroup.shouldResetPrimaryKey(), copyGroup.shouldResetVersion()); |
| if(nonReferenceEntityFetchGroup != null) { |
| mappingCopyGroup = nonReferenceEntityFetchGroup.toCopyGroup(); |
| } else { |
| // null nonReferenceEntityFetchGroup is equivalent to containing all attributes: |
| // create a new empty CopyGroup. |
| mappingCopyGroup = new CopyGroup(); |
| mappingCopyGroup.shouldCascadeTree(); |
| } |
| } else { |
| // TODO: would that work? |
| mappingCopyGroup = new CopyGroup(); |
| mappingCopyGroup.dontCascade(); |
| isVisiting = false; |
| } |
| mappingCopyGroup.setCopies(copyGroup.getCopies()); |
| mappingCopyGroup.setShouldResetPrimaryKey(copyGroup.shouldResetPrimaryKey()); |
| mappingCopyGroup.setShouldResetVersion(copyGroup.shouldResetVersion()); |
| } |
| mappingCopyGroup.setSession(copyGroup.getSession()); |
| } |
| } else if (mapping.isAggregateObjectMapping()) { |
| mappingCopyGroup = new CopyGroup(); |
| } |
| if(shouldCopy || isVisiting) { |
| // TODO: optimization: (even when isVisiting == true) redefine buildCopy to take shouldCopy and don't copy if not required. |
| mapping.buildCopy(copy, original, mappingCopyGroup); |
| } |
| } |
| } |
| } else { |
| // fetchGroupManager == null |
| // TODO |
| } |
| |
| } else { |
| // ! copyGroup.shouldCascadeTree() |
| if (copy != null) { |
| return copy; |
| } |
| |
| copy = instantiateClone(original, copyGroup.getSession()); |
| copyGroup.getCopies().put(original, copy); |
| |
| // PERF: Avoid synchronized enumerator as is concurrency bottleneck. |
| List mappings = getCloningMappings(); |
| int size = mappings.size(); |
| for (int index = 0; index < size; index++) { |
| ((DatabaseMapping)mappings.get(index)).buildCopy(copy, original, copyGroup); |
| } |
| |
| if (copyGroup.shouldResetPrimaryKey() && (!(this.descriptor.isDescriptorTypeAggregate()))) { |
| // Do not reset if any of the keys is mapped through a 1-1, i.e. back reference id has already changed. |
| boolean hasOneToOne = false; |
| List primaryKeyMappings = getPrimaryKeyMappings(); |
| size = primaryKeyMappings.size(); |
| for (int index = 0; index < size; index++) { |
| if (((DatabaseMapping)primaryKeyMappings.get(index)).isOneToOneMapping()) { |
| hasOneToOne = true; |
| } |
| } |
| if (!hasOneToOne) { |
| for (int index = 0; index < size; index++) { |
| DatabaseMapping mapping = (DatabaseMapping)primaryKeyMappings.get(index); |
| |
| // Only null out direct mappings, as others will be nulled in the respective objects. |
| if (mapping.isAbstractColumnMapping()) { |
| Object nullValue = ((AbstractColumnMapping)mapping).getObjectValue(null, copyGroup.getSession()); |
| mapping.setAttributeValueInObject(copy, nullValue); |
| } else if (mapping.isTransformationMapping()) { |
| mapping.setAttributeValueInObject(copy, null); |
| } |
| } |
| } |
| } |
| |
| // PERF: Avoid events if no listeners. |
| if (this.descriptor.getEventManager().hasAnyEventListeners()) { |
| org.eclipse.persistence.descriptors.DescriptorEvent event = new org.eclipse.persistence.descriptors.DescriptorEvent(copy); |
| event.setSession(copyGroup.getSession()); |
| event.setOriginalObject(original); |
| event.setEventCode(DescriptorEventManager.PostCloneEvent); |
| this.descriptor.getEventManager().executeEvent(event); |
| } |
| } |
| |
| return copy; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used by the ObjectBuilder to create an ObjectChangeSet for the specified clone object. |
| * @return ObjectChangeSet the newly created changeSet representing the clone object |
| * @param clone the object to convert to a changeSet. |
| * @param uowChangeSet the owner of this changeSet. |
| */ |
| public ObjectChangeSet createObjectChangeSet(Object clone, UnitOfWorkChangeSet uowChangeSet, AbstractSession session) { |
| boolean isNew = ((UnitOfWorkImpl)session).isCloneNewObject(clone); |
| return createObjectChangeSet(clone, uowChangeSet, isNew, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Used by the ObjectBuilder to create an ObjectChangeSet for the specified clone object. |
| * @return ObjectChangeSet the newly created changeSet representing the clone object |
| * @param clone the object to convert to a changeSet. |
| * @param uowChangeSet the owner of this changeSet. |
| * @param isNew signifies if the clone object is a new object. |
| */ |
| public ObjectChangeSet createObjectChangeSet(Object clone, UnitOfWorkChangeSet uowChangeSet, boolean isNew, AbstractSession session) { |
| return createObjectChangeSet(clone, uowChangeSet, isNew, false, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Used by the ObjectBuilder to create an ObjectChangeSet for the specified clone object. |
| * @return ObjectChangeSet the newly created changeSet representing the clone object |
| * @param clone the object to convert to a changeSet. |
| * @param uowChangeSet the owner of this changeSet. |
| * @param isNew signifies if the clone object is a new object. |
| * @param assignPrimaryKeyIfExisting signifies if the primary key of the change set should be updated if existing. |
| */ |
| public ObjectChangeSet createObjectChangeSet(Object clone, UnitOfWorkChangeSet uowChangeSet, boolean isNew, boolean assignPrimaryKeyIfExisting, AbstractSession session) { |
| ObjectChangeSet changes = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(clone); |
| if (changes == null || 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 = (DatabaseField)primaryKeyFields.get(index); |
| subExpression = ((DatasourcePlatform)session.getDatasourcePlatform()).createExpressionFor(primaryKeyField, builder); |
| |
| if (expression == null) { |
| expression = subExpression; |
| } else { |
| expression = expression.and(subExpression); |
| } |
| } |
| } |
| |
| setPrimaryKeyExpression(expression); |
| } |
| |
| /** |
| * Return the row with primary keys and their values from the given expression. |
| */ |
| public Object extractPrimaryKeyFromExpression(boolean requiresExactMatch, Expression expression, AbstractRecord translationRow, AbstractSession session) { |
| AbstractRecord primaryKeyRow = createRecord(getPrimaryKeyMappings().size(), session); |
| |
| expression.getBuilder().setSession(session.getRootSession(null)); |
| // Get all the field & values from expression. |
| boolean isValid = expression.extractPrimaryKeyValues(requiresExactMatch, this.descriptor, primaryKeyRow, translationRow); |
| if (requiresExactMatch && (!isValid)) { |
| return null; |
| } |
| |
| // Check that the sizes match. |
| if (primaryKeyRow.size() != this.descriptor.getPrimaryKeyFields().size()) { |
| return null; |
| } |
| |
| Object primaryKey = extractPrimaryKeyFromRow(primaryKeyRow, session); |
| if ((primaryKey == null) && isValid) { |
| return InvalidObject.instance; |
| } |
| return primaryKey; |
| } |
| |
| /** |
| * Return if the expression is by primary key. |
| */ |
| public boolean isPrimaryKeyExpression(boolean requiresExactMatch, Expression expression, AbstractSession session) { |
| expression.getBuilder().setSession(session.getRootSession(null)); |
| List<DatabaseField> keyFields = this.descriptor.getPrimaryKeyFields(); |
| int size = keyFields.size(); |
| Set<DatabaseField> fields = new HashSet(size); |
| boolean isValid = expression.extractFields(requiresExactMatch, true, this.descriptor, keyFields, fields); |
| if (requiresExactMatch && (!isValid)) { |
| return false; |
| } |
| // Check that the sizes match. |
| if (fields.size() != size) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Extract primary key attribute values from the domainObject. |
| */ |
| @Override |
| public Object extractPrimaryKeyFromObject(Object domainObject, AbstractSession session) { |
| return extractPrimaryKeyFromObject(domainObject, session, false); |
| } |
| |
| /** |
| * Extract primary key attribute values from the domainObject. |
| */ |
| public Object extractPrimaryKeyFromObject(Object domainObject, AbstractSession session, boolean shouldReturnNullIfNull) { |
| if (domainObject == null) { |
| return null; |
| } |
| // Avoid using the cached id for XML, as the relational descriptor may be different than the xml one. |
| boolean isPersistenceEntity = (domainObject instanceof PersistenceEntity) && (!isXMLObjectBuilder()); |
| if (isPersistenceEntity) { |
| Object primaryKey = ((PersistenceEntity)domainObject)._persistence_getId(); |
| if (primaryKey != null) { |
| return primaryKey; |
| } |
| } |
| ClassDescriptor descriptor = this.descriptor; |
| boolean isNull = false; |
| // Allow for inheritance, the concrete descriptor must always be used. |
| if (descriptor.hasInheritance() && (domainObject.getClass() != descriptor.getJavaClass()) && (!domainObject.getClass().getSuperclass().equals(descriptor.getJavaClass()))) { |
| return session.getDescriptor(domainObject).getObjectBuilder().extractPrimaryKeyFromObject(domainObject, session, shouldReturnNullIfNull); |
| } |
| |
| CacheKeyType cacheKeyType = descriptor.getCachePolicy().getCacheKeyType(); |
| List<DatabaseField> primaryKeyFields = descriptor.getPrimaryKeyFields(); |
| Object[] primaryKeyValues = null; |
| if (cacheKeyType != CacheKeyType.ID_VALUE) { |
| primaryKeyValues = new Object[primaryKeyFields.size()]; |
| } |
| List<DatabaseMapping> mappings = getPrimaryKeyMappings(); |
| int size = mappings.size(); |
| // PERF: optimize simple case of direct mapped singleton primary key. |
| if (descriptor.hasSimplePrimaryKey()) { |
| // PERF: use index not enumeration. |
| for (int index = 0; index < size; index++) { |
| AbstractColumnMapping mapping = (AbstractColumnMapping)mappings.get(index); |
| Object keyValue = mapping.valueFromObject(domainObject, primaryKeyFields.get(index), session); |
| if (isPrimaryKeyComponentInvalid(keyValue, index)) { |
| if (shouldReturnNullIfNull) { |
| return null; |
| } |
| isNull = true; |
| } |
| if (cacheKeyType == CacheKeyType.ID_VALUE) { |
| if (isPersistenceEntity && (!isNull)) { |
| ((PersistenceEntity)domainObject)._persistence_setId(keyValue); |
| } |
| return keyValue; |
| } else { |
| primaryKeyValues[index] = keyValue; |
| } |
| } |
| } else { |
| AbstractRecord databaseRow = createRecordForPKExtraction(size, session); |
| Set<DatabaseMapping> 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<Class> primaryKeyClassifications = getPrimaryKeyClassifications(); |
| Platform platform = session.getPlatform(domainObject.getClass()); |
| // PERF: use index not enumeration |
| for (int index = 0; index < size; index++) { |
| // Ensure that the type extracted from the object is the same type as in the descriptor, |
| // the main reason for this is that 1-1 can optimize on vh by getting from the row as the row-type. |
| Class classification = primaryKeyClassifications.get(index); |
| Object value = databaseRow.get(primaryKeyFields.get(index)); |
| if (isPrimaryKeyComponentInvalid(value, index)) { |
| if (shouldReturnNullIfNull) { |
| return null; |
| } |
| isNull = true; |
| } |
| value = platform.convertObject(value, classification); |
| if (cacheKeyType == CacheKeyType.ID_VALUE) { |
| if (isPersistenceEntity && (!isNull)) { |
| ((PersistenceEntity)domainObject)._persistence_setId(value); |
| } |
| return value; |
| } else { |
| primaryKeyValues[index] = value; |
| } |
| } |
| } |
| CacheId id = new CacheId(primaryKeyValues); |
| if (isPersistenceEntity && (!isNull)) { |
| ((PersistenceEntity)domainObject)._persistence_setId(id); |
| } |
| return id; |
| } |
| |
| /** |
| * Extract primary key values from the specified row. |
| * null is returned if the row does not contain the key. |
| */ |
| public Object extractPrimaryKeyFromRow(AbstractRecord databaseRow, AbstractSession session) { |
| if (databaseRow.hasSopObject()) { |
| // Entity referencing ForeignReferenceMapping has set attribute extracted from sopObject as a sopObject into a new empty row. |
| return extractPrimaryKeyFromObject(databaseRow.getSopObject(), session); |
| } |
| List<DatabaseField> primaryKeyFields = this.descriptor.getPrimaryKeyFields(); |
| if(null == primaryKeyFields) { |
| return null; |
| } |
| List<Class> primaryKeyClassifications = getPrimaryKeyClassifications(); |
| int size = primaryKeyFields.size(); |
| Object[] primaryKeyValues = null; |
| CacheKeyType cacheKeyType = this.descriptor.getCachePolicy().getCacheKeyType(); |
| if (cacheKeyType != CacheKeyType.ID_VALUE) { |
| primaryKeyValues = new Object[size]; |
| } |
| int numberOfNulls = 0; |
| |
| // PERF: use index not enumeration |
| for (int index = 0; index < size; index++) { |
| DatabaseField field = primaryKeyFields.get(index); |
| |
| // Ensure that the type extracted from the row is the same type as in the object. |
| Class classification = primaryKeyClassifications.get(index); |
| Object value = databaseRow.get(field); |
| if (value != null) { |
| if (value.getClass() != classification) { |
| value = session.getPlatform(this.descriptor.getJavaClass()).convertObject(value, classification); |
| } |
| if (cacheKeyType == CacheKeyType.ID_VALUE) { |
| return value; |
| } |
| primaryKeyValues[index] = value; |
| } else { |
| if (this.mayHaveNullInPrimaryKey) { |
| numberOfNulls++; |
| if (numberOfNulls < size) { |
| primaryKeyValues[index] = null; |
| } else { |
| // Must have some non null elements. If all elements are null return null. |
| return null; |
| } |
| } else { |
| return null; |
| } |
| } |
| } |
| |
| return new CacheId(primaryKeyValues); |
| } |
| |
| /** |
| * Return the row with primary keys and their values from the given expression. |
| */ |
| public AbstractRecord extractPrimaryKeyRowFromExpression(Expression expression, AbstractRecord translationRow, AbstractSession session) { |
| if (translationRow != null && translationRow.hasSopObject()) { |
| return translationRow; |
| } |
| AbstractRecord primaryKeyRow = createRecord(getPrimaryKeyMappings().size(), session); |
| |
| expression.getBuilder().setSession(session.getRootSession(null)); |
| // Get all the field & values from expression |
| boolean isValid = expression.extractPrimaryKeyValues(true, this.descriptor, primaryKeyRow, translationRow); |
| if (!isValid) { |
| return null; |
| } |
| |
| // Check that the sizes match up |
| if (primaryKeyRow.size() != this.descriptor.getPrimaryKeyFields().size()) { |
| return null; |
| } |
| |
| return primaryKeyRow; |
| } |
| |
| /** |
| * Return the row from the given expression. |
| */ |
| public AbstractRecord extractRowFromExpression(Expression expression, AbstractRecord translationRow, AbstractSession session) { |
| AbstractRecord record = createRecord(session); |
| |
| expression.getBuilder().setSession(session.getRootSession(null)); |
| // Get all the field & values from expression |
| boolean isValid = expression.extractValues(false, false, this.descriptor, record, translationRow); |
| if (!isValid) { |
| return null; |
| } |
| |
| return record; |
| } |
| |
| /** |
| * Extract primary key attribute values from the domainObject. |
| */ |
| public AbstractRecord extractPrimaryKeyRowFromObject(Object domainObject, AbstractSession session) { |
| AbstractRecord databaseRow = createRecord(getPrimaryKeyMappings().size(), session); |
| |
| // PERF: use index not enumeration. |
| for (int index = 0; index < getPrimaryKeyMappings().size(); index++) { |
| getPrimaryKeyMappings().get(index).writeFromObjectIntoRow(domainObject, databaseRow, session, WriteType.UNDEFINED); |
| } |
| |
| // PERF: optimize simple primary key case, no need to remap. |
| if (this.descriptor.hasSimplePrimaryKey()) { |
| return databaseRow; |
| } |
| AbstractRecord primaryKeyRow = createRecord(getPrimaryKeyMappings().size(), session); |
| List primaryKeyFields = this.descriptor.getPrimaryKeyFields(); |
| for (int index = 0; index < primaryKeyFields.size(); index++) { |
| // Ensure that the type extracted from the object is the same type as in the descriptor, |
| // the main reason for this is that 1-1 can optimize on vh by getting from the row as the row-type. |
| Class classification = getPrimaryKeyClassifications().get(index); |
| DatabaseField field = (DatabaseField)primaryKeyFields.get(index); |
| Object value = databaseRow.get(field); |
| primaryKeyRow.put(field, session.getPlatform(domainObject.getClass()).convertObject(value, classification)); |
| } |
| |
| return primaryKeyRow; |
| } |
| |
| /** |
| * Extract the value of the primary key attribute from the specified object. |
| */ |
| public Object extractValueFromObjectForField(Object domainObject, DatabaseField field, AbstractSession session) throws DescriptorException { |
| // Allow for inheritance, the concrete descriptor must always be used. |
| ClassDescriptor descriptor = null;//this variable will be assigned in the final |
| |
| if (this.descriptor.hasInheritance() && (domainObject.getClass() != this.descriptor.getJavaClass()) && ((descriptor = session.getDescriptor(domainObject)).getJavaClass() != this.descriptor.getJavaClass())) { |
| if(descriptor.isAggregateCollectionDescriptor()) { |
| descriptor = this.descriptor.getInheritancePolicy().getDescriptor(descriptor.getJavaClass()); |
| } |
| return descriptor.getObjectBuilder().extractValueFromObjectForField(domainObject, field, session); |
| } else { |
| DatabaseMapping mapping = getMappingForField(field); |
| if (mapping == null) { |
| throw DescriptorException.missingMappingForField(field, this.descriptor); |
| } |
| |
| return mapping.valueFromObject(domainObject, field, session); |
| } |
| } |
| /** |
| * INTERNAL: |
| * An object has been serialized from the server to the client. |
| * Replace the transient attributes of the remote value holders |
| * with client-side objects. |
| */ |
| public void fixObjectReferences(Object object, Map objectDescriptors, Map processedObjects, ObjectLevelReadQuery query, DistributedSession session) { |
| // PERF: Only process relationships. |
| if (!this.isSimple) { |
| List<DatabaseMapping> mappings = this.relationshipMappings; |
| for (int index = 0; index < mappings.size(); index++) { |
| mappings.get(index).fixObjectReferences(object, objectDescriptors, processedObjects, query, session); |
| } |
| } |
| } |
| |
| /** |
| * Return the base ChangeRecord for the given DatabaseField. |
| * The object and all its relevant aggregates must exist. |
| * The returned ChangeRecord is |
| * either DirectToFieldChangeRecord or TransformationMappingChangeRecord, |
| * or null. |
| */ |
| public ChangeRecord getBaseChangeRecordForField(ObjectChangeSet objectChangeSet, Object object, DatabaseField databaseField, AbstractSession session) { |
| DatabaseMapping mapping = getMappingForField(databaseField); |
| |
| // Drill down through the mappings until we get the direct mapping to the databaseField. |
| while (mapping.isAggregateObjectMapping()) { |
| String attributeName = mapping.getAttributeName(); |
| Object aggregate = mapping.getAttributeValueFromObject(object); |
| ClassDescriptor referenceDescriptor = 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<DatabaseField, DatabaseField> getFieldsMap() { |
| return fieldsMap; |
| } |
| |
| /** |
| * Return the fields map. |
| * Used to maintain identity on the field objects. Ensure they get the correct index/type. |
| */ |
| protected void setFieldsMap(Map fieldsMap) { |
| this.fieldsMap = fieldsMap; |
| } |
| |
| /** |
| * PERF: |
| * Return all mappings that require cloning. |
| * This allows for simple directs to be avoided when using clone copying. |
| */ |
| public List<DatabaseMapping> getCloningMappings() { |
| return cloningMappings; |
| } |
| |
| /** |
| * PERF: |
| * Return if the descriptor has no complex mappings, all direct. |
| */ |
| public boolean isSimple() { |
| return isSimple; |
| } |
| |
| /** |
| * PERF: |
| * Return all relationship mappings. |
| */ |
| public List<DatabaseMapping> getRelationshipMappings() { |
| return relationshipMappings; |
| } |
| |
| /** |
| * PERF: |
| * Return all mappings that are eager loaded (but use indirection). |
| * This allows for eager mappings to still benefit from indirection for locking and change tracking. |
| */ |
| public List<DatabaseMapping> getEagerMappings() { |
| return eagerMappings; |
| } |
| |
| /** |
| * Answers the attributes which are always joined to the original query on reads. |
| */ |
| public List<DatabaseMapping> getJoinedAttributes() { |
| return joinedAttributes; |
| } |
| |
| /** |
| * Return the mappings that are always batch fetched. |
| */ |
| public List<DatabaseMapping> getBatchFetchedAttributes() { |
| return this.batchFetchedAttributes; |
| } |
| |
| /** |
| * PERF: |
| * Return the sequence mapping. |
| */ |
| public AbstractDirectMapping getSequenceMapping() { |
| return sequenceMapping; |
| } |
| |
| /** |
| * PERF: |
| * Set the sequence mapping. |
| */ |
| public void setSequenceMapping(AbstractDirectMapping sequenceMapping) { |
| this.sequenceMapping = sequenceMapping; |
| } |
| |
| /** |
| * Answers if any attributes are to be joined / returned in the same select |
| * statement. |
| */ |
| public boolean hasJoinedAttributes() { |
| return (this.joinedAttributes != null); |
| } |
| |
| /** |
| * Return is any mappings are always batch fetched. |
| */ |
| public boolean hasBatchFetchedAttributes() { |
| return (this.batchFetchedAttributes != null); |
| } |
| |
| /** |
| * Return is any mappings are always batch fetched using IN. |
| */ |
| public boolean hasInBatchFetchedAttribute() { |
| return this.hasInBatchFetchedAttribute; |
| } |
| |
| /** |
| * Set if any mappings are always batch fetched using IN. |
| */ |
| public void setHasInBatchFetchedAttribute(boolean hasInBatchFetchedAttribute) { |
| this.hasInBatchFetchedAttribute = hasInBatchFetchedAttribute; |
| } |
| |
| /** |
| * Return the mapping for the specified attribute name. |
| */ |
| public DatabaseMapping getMappingForAttributeName(String name) { |
| return getMappingsByAttribute().get(name); |
| } |
| |
| /** |
| * Return al the mapping for the specified field. |
| */ |
| @Override |
| public DatabaseMapping getMappingForField(DatabaseField field) { |
| return getMappingsByField().get(field); |
| } |
| |
| /** |
| * Return all the read-only mapping for the specified field. |
| */ |
| public List<DatabaseMapping> getReadOnlyMappingsForField(DatabaseField field) { |
| return getReadOnlyMappingsByField().get(field); |
| } |
| |
| /** |
| * Return all the mapping to attribute associations |
| */ |
| protected Map<String, DatabaseMapping> getMappingsByAttribute() { |
| return mappingsByAttribute; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return all the mapping to field associations |
| */ |
| public Map<DatabaseField, DatabaseMapping> getMappingsByField() { |
| return mappingsByField; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return all the read-only mapping to field associations |
| */ |
| public Map<DatabaseField, List<DatabaseMapping>> getReadOnlyMappingsByField() { |
| return readOnlyMappingsByField; |
| } |
| |
| /** |
| * Return the non primary key mappings. |
| */ |
| protected List<DatabaseMapping> getNonPrimaryKeyMappings() { |
| return nonPrimaryKeyMappings; |
| } |
| |
| /** |
| * Return the base value that is mapped to for given field. |
| */ |
| public Object getParentObjectForField(DatabaseField databaseField, Object domainObject) { |
| Object valueIntoObject = domainObject; |
| DatabaseMapping mapping = getMappingForField(databaseField); |
| |
| // Drill down through the aggregate mappings to get to the direct to field mapping. |
| while (mapping.isAggregateObjectMapping()) { |
| valueIntoObject = mapping.getAttributeValueFromObject(valueIntoObject); |
| mapping = mapping.getReferenceDescriptor().getObjectBuilder().getMappingForField(databaseField); |
| } |
| return valueIntoObject; |
| } |
| |
| /** |
| * Return primary key classifications. |
| * These are used to ensure a consistent type for the pk values. |
| */ |
| public List<Class> getPrimaryKeyClassifications() { |
| if (primaryKeyClassifications == null) { |
| List primaryKeyFields = this.descriptor.getPrimaryKeyFields(); |
| if(null == primaryKeyFields) { |
| return Collections.emptyList(); |
| } |
| List<Class> classifications = new ArrayList(primaryKeyFields.size()); |
| |
| for (int index = 0; index < primaryKeyFields.size(); index++) { |
| if (getPrimaryKeyMappings().size() < (index + 1)) { // Check for failed initialization to avoid cascaded errors. |
| classifications.add(null); |
| } else { |
| DatabaseMapping mapping = getPrimaryKeyMappings().get(index); |
| DatabaseField field = (DatabaseField)primaryKeyFields.get(index); |
| if (mapping != null) { |
| classifications.add(Helper.getObjectClass(mapping.getFieldClassification(field))); |
| } else { |
| classifications.add(null); |
| } |
| } |
| } |
| primaryKeyClassifications = classifications; |
| } |
| return primaryKeyClassifications; |
| } |
| |
| /** |
| * Return the primary key expression |
| */ |
| public Expression getPrimaryKeyExpression() { |
| return primaryKeyExpression; |
| } |
| |
| /** |
| * Return primary key mappings. |
| */ |
| public List<DatabaseMapping> getPrimaryKeyMappings() { |
| return primaryKeyMappings; |
| } |
| |
| /** |
| * INTERNAL: return a database field based on a query key name |
| */ |
| public DatabaseField getTargetFieldForQueryKeyName(String queryKeyName) { |
| DatabaseMapping mapping = getMappingForAttributeName(queryKeyName); |
| if ((mapping != null) && mapping.isAbstractColumnMapping()) { |
| return 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<DatabaseField> indexFields : this.descriptor.getCachePolicy().getCacheIndexes().keySet()) { |
| if (!this.descriptor.getSerializedObjectPolicy().getSelectionFields().containsAll(indexFields)) { |
| this.hasCacheIndexesInSopObject = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Iterates through all one to one mappings and checks if any of them use joining. |
| * <p> |
| * By caching the result query execution in the case where there are no joined |
| * attributes can be improved. |
| */ |
| public void initializeJoinedAttributes() { |
| // For concurrency don't worry about doing this work twice, just make sure |
| // if it happens don't add the same joined attributes twice. |
| List<DatabaseMapping> joinedAttributes = null; |
| List mappings = this.descriptor.getMappings(); |
| for (int i = 0; i < mappings.size(); i++) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.get(i); |
| if (mapping.isForeignReferenceMapping() && ((ForeignReferenceMapping)mapping).isJoinFetched()) { |
| if (joinedAttributes == null) { |
| joinedAttributes = new ArrayList(); |
| } |
| joinedAttributes.add(mapping); |
| } |
| } |
| this.joinedAttributes = joinedAttributes; |
| } |
| |
| /** |
| * INTERNAL: |
| * Iterates through all one to one mappings and checks if any of them use batch fetching. |
| * <p> |
| * By caching the result query execution in the case where there are no batch fetched |
| * attributes can be improved. |
| */ |
| public void initializeBatchFetchedAttributes() { |
| List<DatabaseMapping> batchedAttributes = null; |
| for (DatabaseMapping mapping : this.descriptor.getMappings()) { |
| if (mapping.isForeignReferenceMapping() && ((ForeignReferenceMapping)mapping).shouldUseBatchReading()) { |
| if (batchedAttributes == null) { |
| batchedAttributes = new ArrayList(); |
| } |
| batchedAttributes.add(mapping); |
| if (((ForeignReferenceMapping)mapping).getBatchFetchType() == BatchFetchType.IN) { |
| this.hasInBatchFetchedAttribute = true; |
| } |
| } else if (mapping.isAggregateObjectMapping()) { |
| if (mapping.getReferenceDescriptor().getObjectBuilder().hasInBatchFetchedAttribute()) { |
| this.hasInBatchFetchedAttribute = true; |
| } |
| } |
| } |
| this.batchFetchedAttributes = batchedAttributes; |
| |
| if (this.hasInBatchFetchedAttribute && this.descriptor.hasInheritance()) { |
| ClassDescriptor parent = this.descriptor.getInheritancePolicy().getParentDescriptor(); |
| while (parent != null) { |
| parent.getObjectBuilder().setHasInBatchFetchedAttribute(true); |
| parent = parent.getInheritancePolicy().getParentDescriptor(); |
| } |
| } |
| } |
| |
| /** |
| * Initialize a cache key. Called by buildObject and now also by |
| * buildWorkingCopyCloneFromRow. |
| */ |
| protected void copyQueryInfoToCacheKey(CacheKey cacheKey, ObjectBuildingQuery query, AbstractRecord databaseRow, AbstractSession session, ClassDescriptor concreteDescriptor) { |
| //CR #4365 - used to prevent infinite recursion on refresh object cascade all |
| cacheKey.setLastUpdatedQueryId(query.getQueryId()); |
| |
| if (concreteDescriptor.usesOptimisticLocking()) { |
| OptimisticLockingPolicy policy = concreteDescriptor.getOptimisticLockingPolicy(); |
| Object cacheValue = policy.getValueToPutInCache(databaseRow, session); |
| |
| //register the object into the IM and set the write lock object |
| cacheKey.setWriteLockValue(cacheValue); |
| } |
| cacheKey.setReadTime(query.getExecutionTime()); |
| } |
| |
| /** |
| * Cache primary key and non primary key mappings. |
| */ |
| public void initializePrimaryKey(AbstractSession session) throws DescriptorException { |
| List primaryKeyFields = this.descriptor.getPrimaryKeyFields(); |
| if ((null == primaryKeyFields || primaryKeyFields.isEmpty()) && getDescriptor().isAggregateCollectionDescriptor()) { |
| // populate primaryKeys with all mapped fields found in the main table. |
| DatabaseTable defaultTable = getDescriptor().getDefaultTable(); |
| Iterator<DatabaseField> it = getDescriptor().getFields().iterator(); |
| while(it.hasNext()) { |
| DatabaseField field = it.next(); |
| if(field.getTable().equals(defaultTable) && getMappingsByField().containsKey(field)) { |
| primaryKeyFields.add(field); |
| } |
| } |
| List<DatabaseField> additionalFields = this.descriptor.getAdditionalAggregateCollectionKeyFields(); |
| for(int i=0; i < additionalFields.size(); i++) { |
| DatabaseField additionalField = additionalFields.get(i); |
| if(!primaryKeyFields.contains(additionalField)) { |
| primaryKeyFields.add(additionalField); |
| } |
| } |
| } |
| createPrimaryKeyExpression(session); |
| |
| if(null != primaryKeyMappings) { |
| primaryKeyMappings.clear(); |
| } |
| |
| // This must be before because the secondary table primary key fields are registered after |
| //but no point doing it if the nonPrimaryKeyMappings collection is null |
| if (nonPrimaryKeyMappings != null) { |
| nonPrimaryKeyMappings.clear(); |
| for (Iterator fields = getMappingsByField().keySet().iterator(); fields.hasNext();) { |
| DatabaseField field = (DatabaseField)fields.next(); |
| if (null ==primaryKeyFields || !primaryKeyFields.contains(field)) { |
| DatabaseMapping mapping = getMappingForField(field); |
| if (!getNonPrimaryKeyMappings().contains(mapping)) { |
| getNonPrimaryKeyMappings().add(mapping); |
| } |
| } |
| } |
| } |
| |
| if(null != primaryKeyFields) { |
| for (int index = 0; index < primaryKeyFields.size(); index++) { |
| DatabaseField primaryKeyField = (DatabaseField)primaryKeyFields.get(index); |
| DatabaseMapping mapping = getMappingForField(primaryKeyField); |
| |
| if (mapping == null) { |
| if(this.descriptor.isDescriptorTypeAggregate()) { |
| this.mayHaveNullInPrimaryKey = true; |
| } else { |
| throw DescriptorException.noMappingForPrimaryKey(primaryKeyField, this.descriptor); |
| } |
| } |
| |
| getPrimaryKeyMappings().add(mapping); |
| if (mapping != null) { |
| mapping.setIsPrimaryKeyMapping(true); |
| } |
| |
| // Use the same mapping to map the additional table primary key fields. |
| // This is required if someone tries to map to one of these fields. |
| if (this.descriptor.hasMultipleTables() && (mapping != null)) { |
| for (Map keyMapping : this.descriptor.getAdditionalTablePrimaryKeyFields().values()) { |
| DatabaseField secondaryField = (DatabaseField) keyMapping.get(primaryKeyField); |
| |
| // This can be null in the custom multiple join case |
| if (secondaryField != null) { |
| getMappingsByField().put(secondaryField, mapping); |
| |
| if (mapping.isAggregateObjectMapping()) { |
| // GF#1153,1391 |
| // If AggregateObjectMapping contain primary keys and the descriptor has multiple tables |
| // AggregateObjectMapping should know the the primary key join columns (secondaryField here) |
| // to handle some cases properly |
| ((AggregateObjectMapping) mapping).addPrimaryKeyJoinField(primaryKeyField, secondaryField); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // PERF: compute if primary key is mapped through direct mappings, |
| // to allow fast extraction. |
| boolean hasSimplePrimaryKey = true; |
| if(null != primaryKeyMappings) { |
| for (int index = 0; index < getPrimaryKeyMappings().size(); index++) { |
| DatabaseMapping mapping = getPrimaryKeyMappings().get(index); |
| |
| // Primary key mapping may be null for aggregate collection. |
| if ((mapping == null) || (!mapping.isAbstractColumnMapping())) { |
| hasSimplePrimaryKey = false; |
| break; |
| } |
| } |
| } |
| this.descriptor.setHasSimplePrimaryKey(hasSimplePrimaryKey); |
| |
| // Set id validation, zero is allowed for composite primary keys. |
| boolean wasIdValidationSet = true; |
| if (this.descriptor.getIdValidation() == null) { |
| wasIdValidationSet = false; |
| List<DatabaseField> descriptorPrimaryKeyFields = this.descriptor.getPrimaryKeyFields(); |
| if (descriptorPrimaryKeyFields != null && descriptorPrimaryKeyFields.size() > 1) { |
| this.descriptor.setIdValidation(IdValidation.NULL); |
| } else { |
| this.descriptor.setIdValidation(IdValidation.ZERO); |
| } |
| } |
| // Initialize id validation per field, default sequence to allowing zero. |
| // This defaults to allowing zero for the other fields. |
| if (this.descriptor.getPrimaryKeyFields() != null && this.descriptor.getPrimaryKeyIdValidations() == null) { |
| this.descriptor.setPrimaryKeyIdValidations(new ArrayList(this.descriptor.getPrimaryKeyFields().size())); |
| for (DatabaseField field : this.descriptor.getPrimaryKeyFields()) { |
| if (!wasIdValidationSet && this.descriptor.usesSequenceNumbers() && field.equals(this.descriptor.getSequenceNumberField())) { |
| this.descriptor.getPrimaryKeyIdValidations().add(IdValidation.ZERO); |
| } else { |
| this.descriptor.getPrimaryKeyIdValidations().add(this.descriptor.getIdValidation()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the clone of the specified object. This is called only from unit of work. |
| * This only instantiates the clone instance, it does not clone the attributes, |
| * this allows the stub of the clone to be registered before cloning its parts. |
| */ |
| public Object instantiateClone(Object domainObject, AbstractSession session) { |
| Object clone = this.descriptor.getCopyPolicy().buildClone(domainObject, session); |
| // Clear change tracker. |
| if (clone instanceof ChangeTracker) { |
| ((ChangeTracker)clone)._persistence_setPropertyChangeListener(null); |
| } |
| if(clone instanceof FetchGroupTracker) { |
| ((FetchGroupTracker)clone)._persistence_setFetchGroup(null); |
| ((FetchGroupTracker)clone)._persistence_setSession(null); |
| } |
| clearPrimaryKey(clone); |
| return clone; |
| } |
| |
| /** |
| * Returns the clone of the specified object. This is called only from unit of work. |
| * The domainObject sent as parameter is always a copy from the parent of unit of work. |
| * bug 2612602 make a call to build a working clone. This will in turn call the copy policy |
| * to make a working clone. This allows for lighter and heavier clones to |
| * be created based on their use. |
| * this allows the stub of the clone to be registered before cloning its parts. |
| */ |
| public Object instantiateWorkingCopyClone(Object domainObject, AbstractSession session) { |
| return this.descriptor.getCopyPolicy().buildWorkingCopyClone(domainObject, session); |
| } |
| |
| /** |
| * It is now possible to build working copy clones directly from rows. |
| * <p>An intermediary original is no longer needed. |
| * <p>This has ramifications to the copy policy and cmp, for clones are |
| * no longer built via cloning. |
| * <p>Instead the copy policy must in some cases not copy at all. |
| * this allows the stub of the clone to be registered before cloning its parts. |
| */ |
| public Object instantiateWorkingCopyCloneFromRow(AbstractRecord row, ObjectBuildingQuery query, Object primaryKey, UnitOfWorkImpl unitOfWork) { |
| return this.descriptor.getCopyPolicy().buildWorkingCopyCloneFromRow(row, query, primaryKey, unitOfWork); |
| } |
| |
| public boolean isPrimaryKeyMapping(DatabaseMapping mapping) { |
| return getPrimaryKeyMappings().contains(mapping); |
| } |
| |
| /** |
| * INTERNAL: |
| * Perform the iteration operation on the objects attributes through the mappings. |
| */ |
| public void iterate(DescriptorIterator iterator) { |
| List<DatabaseMapping> mappings; |
| // Only iterate on relationships if required. |
| if (iterator.shouldIterateOnPrimitives()) { |
| mappings = this.descriptor.getMappings(); |
| } else { |
| // PERF: Only process relationships. |
| if (this.isSimple) { |
| return; |
| } |
| mappings = this.relationshipMappings; |
| } |
| int mappingsSize = mappings.size(); |
| for (int index = 0; index < mappingsSize; index++) { |
| mappings.get(index).iterate(iterator); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes between the objects, this merge algorithm is dependent on the merge manager. |
| */ |
| public void mergeChangesIntoObject(Object target, ObjectChangeSet changeSet, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| mergeChangesIntoObject(target, changeSet, source, mergeManager, targetSession, false, false); |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes between the objects, this merge algorithm is dependent on the merge manager. |
| */ |
| public void mergeChangesIntoObject(Object target, ObjectChangeSet changeSet, Object source, MergeManager mergeManager, AbstractSession targetSession, boolean isTargetCloneOfOriginal, boolean shouldMergeFetchGroup) { |
| // PERF: Just merge the object for new objects, as the change set is not populated. |
| if ((source != null) && changeSet.isNew() && (!this.descriptor.shouldUseFullChangeSetsForNewObjects())) { |
| mergeIntoObject(target, changeSet, true, source, mergeManager, targetSession, false, isTargetCloneOfOriginal, shouldMergeFetchGroup); |
| } else { |
| List changes = changeSet.getChanges(); |
| int size = changes.size(); |
| for (int index = 0; index < size; index++) { |
| ChangeRecord record = (ChangeRecord)changes.get(index); |
| //cr 4236, use ObjectBuilder getMappingForAttributeName not the Descriptor one because the |
| // ObjectBuilder method is much more efficient. |
| DatabaseMapping mapping = getMappingForAttributeName(record.getAttribute()); |
| mapping.mergeChangesIntoObject(target, record, source, mergeManager, targetSession); |
| } |
| // PERF: Avoid events if no listeners. |
| // Event is already raised in mergeIntoObject, avoid calling twice. |
| if (this.descriptor.getEventManager().hasAnyEventListeners()) { |
| DescriptorEvent event = new DescriptorEvent(target); |
| event.setSession(mergeManager.getSession()); |
| event.setOriginalObject(source); |
| event.setChangeSet(changeSet); |
| event.setEventCode(DescriptorEventManager.PostMergeEvent); |
| this.descriptor.getEventManager().executeEvent(event); |
| } |
| } |
| this.descriptor.getCachePolicy().indexObjectInCache(changeSet, target, this.descriptor, targetSession); |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge the contents of one object into another, this merge algorithm is dependent on the merge manager. |
| * This merge also prevents the extra step of calculating the changes when it is not required. |
| */ |
| public void mergeIntoObject(Object target, boolean isUnInitialized, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| mergeIntoObject(target, null, isUnInitialized, source, mergeManager, targetSession, false, false, false); |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge the contents of one object into another, this merge algorithm is dependent on the merge manager. |
| * This merge also prevents the extra step of calculating the changes when it is not required. |
| * If 'cascadeOnly' is true, only foreign reference mappings are merged. |
| * If 'isTargetCloneOfOriginal' then the target was create through a shallow clone of the source, so merge basics is not required. |
| */ |
| public void mergeIntoObject(Object target, ObjectChangeSet changeSet, boolean isUnInitialized, Object source, MergeManager mergeManager, AbstractSession targetSession, boolean cascadeOnly, boolean isTargetCloneOfOriginal, boolean shouldMergeFetchGroup) { |
| // cascadeOnly is introduced to optimize merge |
| // for GF#1139 Cascade merge operations to relationship mappings even if already registered |
| |
| FetchGroup sourceFetchGroup = null; |
| FetchGroup targetFetchGroup = null; |
| if(this.descriptor.hasFetchGroupManager()) { |
| sourceFetchGroup = this.descriptor.getFetchGroupManager().getObjectFetchGroup(source); |
| targetFetchGroup = this.descriptor.getFetchGroupManager().getObjectFetchGroup(target); |
| if(targetFetchGroup != null) { |
| if(!targetFetchGroup.isSupersetOf(sourceFetchGroup)) { |
| targetFetchGroup.onUnfetchedAttribute((FetchGroupTracker)target, null); |
| } |
| } else if (shouldMergeFetchGroup && sourceFetchGroup != null){ |
| this.descriptor.getFetchGroupManager().setObjectFetchGroup(target, sourceFetchGroup, targetSession); |
| } |
| } |
| // PERF: Avoid synchronized enumerator as is concurrency bottleneck. |
| List<DatabaseMapping> mappings = this.descriptor.getMappings(); |
| int size = mappings.size(); |
| for (int index = 0; index < size; index++) { |
| DatabaseMapping mapping = mappings.get(index); |
| if (((!cascadeOnly && !isTargetCloneOfOriginal) |
| || (cascadeOnly && mapping.isForeignReferenceMapping()) |
| || (isTargetCloneOfOriginal && mapping.isCloningRequired())) |
| && (sourceFetchGroup == null || sourceFetchGroup.containsAttributeInternal(mapping.getAttributeName()))) { |
| mapping.mergeIntoObject(target, isUnInitialized, source, mergeManager, targetSession); |
| } |
| } |
| |
| // PERF: Avoid events if no listeners. |
| if (this.descriptor.getEventManager().hasAnyEventListeners()) { |
| DescriptorEvent event = new DescriptorEvent(target); |
| event.setSession(mergeManager.getSession()); |
| event.setOriginalObject(source); |
| event.setChangeSet(changeSet); |
| event.setEventCode(DescriptorEventManager.PostMergeEvent); |
| this.descriptor.getEventManager().executeEvent(event); |
| } |
| } |
| |
| /** |
| * Clones the attributes of the specified object. This is called only from unit of work. |
| * The domainObject sent as parameter is always a copy from the parent of unit of work. |
| */ |
| public void populateAttributesForClone(Object original, CacheKey cacheKey, Object clone, Integer refreshCascade, AbstractSession cloningSession) { |
| List mappings = getCloningMappings(); |
| int size = mappings.size(); |
| if (this.descriptor.hasFetchGroupManager() && this.descriptor.getFetchGroupManager().isPartialObject(original)) { |
| FetchGroupManager fetchGroupManager = this.descriptor.getFetchGroupManager(); |
| for (int index = 0; index < size; index++) { |
| DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); |
| if (fetchGroupManager.isAttributeFetched(original, mapping.getAttributeName())) { |
| mapping.buildClone(original, cacheKey, clone, refreshCascade, cloningSession); |
| } |
| } |
| } else { |
| for (int index = 0; index < size; index++) { |
| ((DatabaseMapping)mappings.get(index)).buildClone(original, cacheKey, clone, refreshCascade, cloningSession); |
| } |
| } |
| |
| // PERF: Avoid events if no listeners. |
| if (this.descriptor.getEventManager().hasAnyEventListeners()) { |
| DescriptorEvent event = new DescriptorEvent(clone); |
| event.setSession(cloningSession); |
| event.setOriginalObject(original); |
| event.setDescriptor(descriptor); |
| event.setEventCode(DescriptorEventManager.PostCloneEvent); |
| cloningSession.deferEvent(event); |
| } |
| } |
| |
| protected void loadBatchReadAttributes(ClassDescriptor concreteDescriptor, Object sourceObject, CacheKey cacheKey, AbstractRecord databaseRow, ObjectBuildingQuery query, JoinedAttributeManager joinManager, boolean isTargetProtected){ |
| boolean useOnlyMappingsExcludedFromSOP = false; |
| if (concreteDescriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy()) { |
| // if true then sopObject has not been deserialized, that means sourceObject has been cached. |
| useOnlyMappingsExcludedFromSOP = databaseRow.get(concreteDescriptor.getSerializedObjectPolicy().getField()) != null; |
| } |
| boolean isUntriggeredResultSetRecord = databaseRow instanceof ResultSetRecord && ((ResultSetRecord)databaseRow).hasResultSet(); |
| List batchExpressions = ((ReadAllQuery)query).getBatchReadAttributeExpressions(); |
| int size = batchExpressions.size(); |
| for (int index = 0; index < size; index++) { |
| QueryKeyExpression queryKeyExpression = (QueryKeyExpression)batchExpressions.get(index); |
| // Only worry about immediate attributes. |
| if (queryKeyExpression.getBaseExpression().isExpressionBuilder()) { |
| DatabaseMapping mapping = getMappingForAttributeName(queryKeyExpression.getName()); |
| |
| if (mapping == null) { |
| throw ValidationException.missingMappingForAttribute(concreteDescriptor, queryKeyExpression.getName(), this.toString()); |
| } else { |
| if (!useOnlyMappingsExcludedFromSOP || mapping.isOutSopObject()) { |
| // Bug 4230655 - do not replace instantiated valueholders. |
| Object attributeValue = mapping.getAttributeValueFromObject(sourceObject); |
| if ((attributeValue != null) && mapping.isForeignReferenceMapping() && ((ForeignReferenceMapping)mapping).usesIndirection() && (!((ForeignReferenceMapping)mapping).getIndirectionPolicy().objectIsInstantiated(attributeValue))) { |
| if (isUntriggeredResultSetRecord && mapping.isObjectReferenceMapping() && ((ObjectReferenceMapping)mapping).isForeignKeyRelationship() && !mapping.isPrimaryKeyMapping()) { |
| // ResultSetRecord hasn't been triggered (still has ResultSet), but values for its primary key field(s) were already extracted from ResultSet, |
| // still need to extract values from ResultSet for foreign key fields. |
| for (DatabaseField field : mapping.getFields()) { |
| // extract the values from ResultSet into the row |
| databaseRow.get(field); |
| } |
| } |
| AbstractSession session = query.getExecutionSession(); |
| mapping.readFromRowIntoObject(databaseRow, joinManager, sourceObject, cacheKey, query, query.getExecutionSession(),isTargetProtected); |
| session.getIdentityMapAccessorInstance().getIdentityMap(concreteDescriptor).lazyRelationshipLoaded(sourceObject, (ValueHolderInterface) ((ForeignReferenceMapping)mapping).getIndirectionPolicy().getOriginalValueHolder(attributeValue, session), (ForeignReferenceMapping)mapping); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| protected void loadJoinedAttributes(ClassDescriptor concreteDescriptor, Object sourceObject, CacheKey cacheKey, AbstractRecord databaseRow, JoinedAttributeManager joinManager, ObjectBuildingQuery query, boolean isTargetProtected){ |
| boolean useOnlyMappingsExcludedFromSOP = false; |
| if (concreteDescriptor.hasSerializedObjectPolicy() && query.shouldUseSerializedObjectPolicy()) { |
| // sopObject has not been deserialized, sourceObject must be cached |
| useOnlyMappingsExcludedFromSOP = databaseRow.get(concreteDescriptor.getSerializedObjectPolicy().getField()) != null; |
| } |
| Boolean isUntriggeredResultSetRecord = null; |
| List joinExpressions = joinManager.getJoinedAttributeExpressions(); |
| int size = joinExpressions.size(); |
| for (int index = 0; index < size; index++) { |
| QueryKeyExpression queryKeyExpression = (QueryKeyExpression)joinExpressions.get(index); |
| QueryKeyExpression baseExpression = (QueryKeyExpression)joinManager.getJoinedAttributes().get(index); |
| DatabaseMapping mapping = joinManager.getJoinedAttributeMappings().get(index); |
| |
| // Only worry about immediate (excluding aggregates) foreign reference mapping attributes. |
| if (queryKeyExpression == baseExpression) { |
| if (mapping == null) { |
| throw ValidationException.missingMappingForAttribute(concreteDescriptor, queryKeyExpression.getName(), toString()); |
| } else { |
| if (!useOnlyMappingsExcludedFromSOP || mapping.isOutSopObject()) { |
| //get the intermediate objects between this expression node and the base builder |
| Object intermediateValue = joinManager.getValueFromObjectForExpression(query.getExecutionSession(), sourceObject, (ObjectExpression)baseExpression.getBaseExpression()); |
| // Bug 4230655 - do not replace instantiated valueholders. |
| Object attributeValue = mapping.getAttributeValueFromObject(intermediateValue); |
| if ((attributeValue != null) && mapping.isForeignReferenceMapping() && ((ForeignReferenceMapping)mapping).usesIndirection() && (!((ForeignReferenceMapping)mapping).getIndirectionPolicy().objectIsInstantiated(attributeValue))) { |
| if (mapping.isObjectReferenceMapping() && ((ObjectReferenceMapping)mapping).isForeignKeyRelationship() && !mapping.isPrimaryKeyMapping()) { |
| if (isUntriggeredResultSetRecord == null) { |
| isUntriggeredResultSetRecord = 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<String, DatabaseMapping> theAttributeMappings) { |
| mappingsByAttribute = theAttributeMappings; |
| } |
| |
| /** |
| * INTERNAL: |
| * All the mappings and their respective field associations are cached for performance improvement. |
| */ |
| public void setMappingsByField(Map<DatabaseField, DatabaseMapping> theFieldMappings) { |
| mappingsByField = theFieldMappings; |
| } |
| |
| /** |
| * INTERNAL: |
| * All the read-only mappings and their respective field associations are cached for performance improvement. |
| */ |
| public void setReadOnlyMappingsByField(Map<DatabaseField, List<DatabaseMapping>> theReadOnlyFieldMappings) { |
| readOnlyMappingsByField = theReadOnlyFieldMappings; |
| } |
| |
| /** |
| * The non primary key mappings are cached to improve performance. |
| */ |
| protected void setNonPrimaryKeyMappings(List<DatabaseMapping> theNonPrimaryKeyMappings) { |
| nonPrimaryKeyMappings = theNonPrimaryKeyMappings; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set primary key classifications. |
| * These are used to ensure a consistent type for the pk values. |
| */ |
| public void setPrimaryKeyClassifications(List<Class> primaryKeyClassifications) { |
| this.primaryKeyClassifications = primaryKeyClassifications; |
| } |
| |
| /** |
| * The primary key expression is cached to improve performance. |
| */ |
| public void setPrimaryKeyExpression(Expression criteria) { |
| primaryKeyExpression = criteria; |
| } |
| |
| /** |
| * The primary key mappings are cached to improve performance. |
| */ |
| protected void setPrimaryKeyMappings(List<DatabaseMapping> thePrimaryKeyMappings) { |
| primaryKeyMappings = thePrimaryKeyMappings; |
| } |
| |
| @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()); |
| } |
| } |