| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2019 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/19/2011-2.2.1 Guy Pelletier |
| // - 338812: ManyToMany mapping in aggregate object violate integrity constraint on deletion |
| // 04/09/2012-2.4 Guy Pelletier |
| // - 374377: OrderBy with ElementCollection doesn't work |
| // 14/05/2012-2.4 Guy Pelletier |
| // - 376603: Provide for table per tenant support for multitenant applications |
| // 30/05/2012-2.4 Guy Pelletier |
| // - 354678: Temp classloader is still being used during metadata processing |
| // 08/01/2012-2.5 Chris Delahunt |
| // - 371950: Metadata caching |
| // 06/03/2013-2.5.1 Guy Pelletier |
| // - 402380: 3 jpa21/advanced tests failed on server with |
| // "java.lang.NoClassDefFoundError: org/eclipse/persistence/testing/models/jpa21/advanced/enums/Gender" |
| package org.eclipse.persistence.mappings; |
| |
| import java.beans.PropertyChangeEvent; |
| import java.security.AccessController; |
| import java.security.PrivilegedActionException; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Vector; |
| |
| import org.eclipse.persistence.annotations.BatchFetchType; |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.descriptors.TablePerMultitenantPolicy; |
| import org.eclipse.persistence.descriptors.changetracking.ChangeTracker; |
| import org.eclipse.persistence.descriptors.changetracking.CollectionChangeEvent; |
| import org.eclipse.persistence.exceptions.ConversionException; |
| 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.history.AsOfClause; |
| import org.eclipse.persistence.history.HistoryPolicy; |
| import org.eclipse.persistence.indirection.IndirectCollection; |
| import org.eclipse.persistence.indirection.IndirectList; |
| import org.eclipse.persistence.indirection.ValueHolder; |
| import org.eclipse.persistence.internal.databaseaccess.Platform; |
| import org.eclipse.persistence.internal.descriptors.DescriptorIterator; |
| import org.eclipse.persistence.internal.descriptors.ObjectBuilder; |
| import org.eclipse.persistence.internal.descriptors.changetracking.AttributeChangeListener; |
| import org.eclipse.persistence.internal.descriptors.changetracking.ObjectChangeListener; |
| import org.eclipse.persistence.internal.expressions.ForUpdateClause; |
| import org.eclipse.persistence.internal.expressions.ObjectExpression; |
| import org.eclipse.persistence.internal.expressions.SQLDeleteStatement; |
| import org.eclipse.persistence.internal.expressions.SQLInsertStatement; |
| import org.eclipse.persistence.internal.expressions.SQLSelectStatement; |
| import org.eclipse.persistence.internal.expressions.SQLUpdateStatement; |
| import org.eclipse.persistence.internal.expressions.TableExpression; |
| import org.eclipse.persistence.internal.helper.ConversionManager; |
| 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.NonSynchronizedVector; |
| import org.eclipse.persistence.internal.identitymaps.CacheId; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.queries.ContainerPolicy; |
| import org.eclipse.persistence.internal.queries.JoinedAttributeManager; |
| import org.eclipse.persistence.internal.queries.OrderedListContainerPolicy; |
| import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; |
| import org.eclipse.persistence.internal.security.PrivilegedClassForName; |
| import org.eclipse.persistence.internal.security.PrivilegedNewInstanceFromClass; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.ChangeRecord; |
| import org.eclipse.persistence.internal.sessions.DirectCollectionChangeRecord; |
| import org.eclipse.persistence.internal.sessions.MergeManager; |
| import org.eclipse.persistence.internal.sessions.ObjectChangeSet; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.internal.sessions.remote.ObjectDescriptor; |
| import org.eclipse.persistence.internal.sessions.remote.RemoteSessionController; |
| import org.eclipse.persistence.mappings.converters.Converter; |
| import org.eclipse.persistence.mappings.converters.ObjectTypeConverter; |
| import org.eclipse.persistence.mappings.converters.SerializedObjectConverter; |
| import org.eclipse.persistence.mappings.converters.TypeConversionConverter; |
| import org.eclipse.persistence.queries.DataModifyQuery; |
| import org.eclipse.persistence.queries.DataReadQuery; |
| import org.eclipse.persistence.queries.DatabaseQuery; |
| import org.eclipse.persistence.queries.DeleteObjectQuery; |
| import org.eclipse.persistence.queries.DirectReadQuery; |
| import org.eclipse.persistence.queries.ModifyQuery; |
| import org.eclipse.persistence.queries.ObjectBuildingQuery; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.queries.QueryByExamplePolicy; |
| import org.eclipse.persistence.queries.ReadAllQuery; |
| import org.eclipse.persistence.queries.ReadQuery; |
| import org.eclipse.persistence.queries.ReportQuery; |
| import org.eclipse.persistence.queries.WriteObjectQuery; |
| import org.eclipse.persistence.sessions.CopyGroup; |
| import org.eclipse.persistence.sessions.DatabaseRecord; |
| import org.eclipse.persistence.sessions.remote.DistributedSession; |
| |
| /** |
| * <p><b>Purpose</b>: This mapping is used to store a collection of simple types (String, Number, Date, etc.) |
| * into a single table. The table must store the value and a foreign key to the source object. |
| * A converter can be used if the desired object type and the data type do not match. |
| * |
| * @see Converter |
| * @see ObjectTypeConverter |
| * @see TypeConversionConverter |
| * @see SerializedObjectConverter |
| * |
| * @author Sati |
| * @since TOPLink/Java 1.0 |
| * |
| * 09/18/2009-2.0 Michael O'Brien |
| * - 266912: JPA 2.0 Metamodel API (part of the JSR-317 EJB 3.1 Criteria API) |
| * add support for passing BasicMap value type to MapAttributeImpl via new attributeClassification field |
| */ |
| public class DirectCollectionMapping extends CollectionMapping implements RelationalMapping { |
| |
| /** Used for data modification events. */ |
| protected static final String Delete = "delete"; |
| protected static final String Insert = "insert"; |
| protected static final String DeleteAll = "deleteAll"; |
| protected static final String DeleteAtIndex = "deleteAtIndex"; |
| protected static final String UpdateAtIndex = "updateAtIndex"; |
| |
| /** Allows user defined conversion between the object value and the database value. */ |
| protected Converter valueConverter; |
| protected String valueConverterClassName; |
| |
| protected List<Expression> orderByExpressions; |
| |
| /** Stores the reference table*/ |
| protected DatabaseTable referenceTable; |
| |
| /** The direct field name is converted and stored */ |
| protected DatabaseField directField; |
| protected Vector<DatabaseField> sourceKeyFields; |
| protected Vector<DatabaseField> referenceKeyFields; |
| |
| /** Used for insertion for m-m and dc, not used in 1-m. */ |
| protected DataModifyQuery insertQuery; |
| |
| /** Used for deletion when ChangeSets are used */ |
| protected ModifyQuery changeSetDeleteQuery; |
| protected transient ModifyQuery changeSetDeleteNullQuery; // Bug 306075 |
| protected boolean hasCustomDeleteQuery; |
| protected boolean hasCustomInsertQuery; |
| protected HistoryPolicy historyPolicy; |
| |
| /** Used (only in case listOrderField != null) to delete object with particular orderFieldValue */ |
| protected ModifyQuery deleteAtIndexQuery; |
| /** Used (only in case listOrderField != null) to update orderFieldValue of object with particular orderFieldValue */ |
| protected ModifyQuery updateAtIndexQuery; |
| protected boolean hasCustomDeleteAtIndexQuery; |
| protected boolean hasCustomUpdateAtIndexQuery; |
| |
| /** |
| * @since Java Persistence API 2.0 |
| * Referenced by MapAttributeImpl to pick up the BasicMap value parameter type |
| * To specify the conversion type |
| * */ |
| protected transient Class<?> attributeClassification; |
| protected String attributeClassificationName; |
| |
| /** |
| * PUBLIC: |
| * Default constructor. |
| */ |
| public DirectCollectionMapping() { |
| this.insertQuery = new DataModifyQuery(); |
| this.orderByExpressions = new ArrayList<>(); |
| this.sourceKeyFields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1); |
| this.referenceKeyFields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1); |
| this.selectionQuery = new DirectReadQuery(); |
| this.hasCustomInsertQuery = false; |
| this.isPrivateOwned = true; |
| this.isListOrderFieldSupported = true; |
| } |
| |
| /** |
| * PUBLIC: |
| * Provide ascending order support for this direct collection mapping. |
| */ |
| public void addAscendingOrdering() { |
| this.hasOrderBy = true; |
| orderByExpressions.add(new ExpressionBuilder().getField(getDirectFieldName()).ascending()); |
| } |
| |
| /** |
| * PUBLIC: |
| * Provide descending order support for this direct collection mapping. |
| */ |
| public void addDescendingOrdering() { |
| this.hasOrderBy = true; |
| orderByExpressions.add(new ExpressionBuilder().getField(getDirectFieldName()).descending()); |
| } |
| |
| /** |
| * ADVANCED: |
| * Used this method to add custom ordering expressions when fetching |
| * the collection. This could be things like expressions using a functions |
| * like UPPER or NULLS LAST etc. |
| */ |
| public void addOrdering(Expression expression) { |
| this.orderByExpressions.add(expression); |
| } |
| |
| @Override |
| public boolean isRelationalMapping() { |
| return true; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the converter on the mapping. |
| * A converter can be used to convert between the direct collection's object value and database value. |
| */ |
| public Converter getValueConverter() { |
| return valueConverter; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the converter on the mapping. |
| * A converter can be used to convert between the direct collection's object value and database value. |
| */ |
| public void setValueConverter(Converter valueConverter) { |
| this.valueConverter = valueConverter; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the converter class name on the mapping. Initialized in |
| * convertClassNamesToClasses. |
| * A converter can be used to convert between the direct collection's object value and database value. |
| */ |
| public void setValueConverterClassName(String valueConverterClassName) { |
| this.valueConverterClassName = valueConverterClassName; |
| } |
| |
| /** |
| * PUBLIC: |
| * Add the reference key field. |
| * This is used for composite reference keys. |
| * This is the foreign key field in the direct table referencing the primary key of the source object. |
| * Both the reference field and the source field that it references must be provided. |
| */ |
| public void addReferenceKeyField(DatabaseField referenceForeignKeyField, DatabaseField sourcePrimaryKeyField) { |
| getSourceKeyFields().addElement(sourcePrimaryKeyField); |
| getReferenceKeyFields().addElement(referenceForeignKeyField); |
| } |
| |
| /** |
| * PUBLIC: |
| * Add the name of the reference key field. |
| * This is used for composite reference keys. |
| * This is the foreign key field in the direct table referencing the primary key of the source object. |
| * Both the reference field name and the name of the source field that it references must be provided. |
| */ |
| public void addReferenceKeyFieldName(String referenceForeignKeyFieldName, String sourcePrimaryKeyFieldName) { |
| addReferenceKeyField(new DatabaseField(referenceForeignKeyFieldName), new DatabaseField(sourcePrimaryKeyFieldName)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone and prepare the selection query as a nested batch read query. |
| * This is used for nested batch reading. |
| */ |
| @Override |
| public ReadQuery prepareNestedBatchQuery(ObjectLevelReadQuery query) { |
| // For CR#2646-S.M. In case of inheritance the descriptor to use may not be that |
| // of the source query (the base class descriptor), but that of the subclass, if the |
| // attribute is only of the subclass. Thus in this case use the descriptor from the mapping. |
| // Also: for Bug 5478648 - Do not switch the descriptor if the query's descriptor is an aggregate |
| ClassDescriptor descriptorToUse = query.getDescriptor(); |
| if ((descriptorToUse != this.descriptor) && (!descriptorToUse.getMappings().contains(this)) && (!this.descriptor.isDescriptorTypeAggregate())) { |
| descriptorToUse = this.descriptor; |
| } |
| DataReadQuery batchQuery = new DataReadQuery(); |
| batchQuery.setName(getAttributeName()); |
| // Join the query where clause with the mappings, |
| // this will cause a join that should bring in all of the target objects. |
| ExpressionBuilder builder; |
| Expression originalSelectionCriteria = null; |
| |
| // 2612538 - the default size of Map (32) is appropriate |
| Map<Expression, Expression> clonedExpressions = new IdentityHashMap<>(); |
| builder = new ExpressionBuilder(); |
| // For flashback. |
| if (query.hasAsOfClause()) { |
| builder.asOf(query.getAsOfClause()); |
| } |
| Expression batchSelectionCriteria = null; |
| |
| // Build the batch query, either using joining, or an exist sub-select. |
| BatchFetchType batchType = query.getBatchFetchPolicy().getType(); |
| if (this.batchFetchType != null) { |
| batchType = this.batchFetchType; |
| } |
| if (batchType == BatchFetchType.EXISTS) { |
| // Using a EXISTS sub-select (WHERE EXIST (<original-query> AND <mapping-join> AND <mapping-join>) |
| ExpressionBuilder subBuilder = new ExpressionBuilder(descriptorToUse.getJavaClass()); |
| subBuilder.setQueryClassAndDescriptor(descriptorToUse.getJavaClass(), descriptorToUse); |
| ReportQuery subQuery = new ReportQuery(descriptorToUse.getJavaClass(), subBuilder); |
| subQuery.setDescriptor(descriptorToUse); |
| subQuery.setShouldRetrieveFirstPrimaryKey(true); |
| Expression subCriteria = subBuilder.twist(getSelectionCriteria(), builder); |
| if (query.getSelectionCriteria() != null) { |
| // For bug 2612567, any query can have batch attributes, so the |
| // original selection criteria can be quite complex, with multiple |
| // builders (i.e. for parallel selects). |
| // Now uses cloneUsing(newBase) instead of rebuildOn(newBase). |
| subCriteria = query.getSelectionCriteria().cloneUsing(subBuilder).and(subCriteria); |
| } |
| subQuery.setSelectionCriteria(subCriteria); |
| batchSelectionCriteria = builder.exists(subQuery); |
| } else if (batchType == BatchFetchType.IN) { |
| // Using a IN with foreign key values (WHERE FK IN :QUERY_BATCH_PARAMETER) |
| batchSelectionCriteria = buildBatchCriteria(builder, query); |
| } else { |
| // For 2729729 must clone the original selection criteria first, |
| // otherwise the original query will be corrupted. |
| if (query.getSelectionCriteria() != null) { |
| originalSelectionCriteria = query.getSelectionCriteria().copiedVersionFrom(clonedExpressions); |
| builder = originalSelectionCriteria.getBuilder(); |
| } |
| |
| // Using a join, (WHERE <orginal-query-criteria> AND <mapping-join>) |
| if (this.selectionQuery.isReadAllQuery()) { |
| batchSelectionCriteria = builder.twist(this.selectionQuery.getSelectionCriteria(), builder); |
| } else { |
| batchSelectionCriteria = builder.twist(this.selectionQuery.getSQLStatement().getWhereClause(), builder); |
| } |
| // For 2729729, rebuildOn is not needed as the base is still the same. |
| if (originalSelectionCriteria != null) { |
| batchSelectionCriteria = batchSelectionCriteria.and(originalSelectionCriteria); |
| } |
| if (descriptorToUse.getQueryManager().getAdditionalJoinExpression() != null) { |
| batchSelectionCriteria = batchSelectionCriteria.and(query.getDescriptor().getQueryManager().getAdditionalJoinExpression().rebuildOn(builder)); |
| } |
| if (this.historyPolicy != null) { |
| if (query.getSession().getAsOfClause() != null) { |
| builder.asOf(query.getSession().getAsOfClause()); |
| } else if (builder.getAsOfClause() == null) { |
| builder.asOf(AsOfClause.NO_CLAUSE); |
| } |
| batchSelectionCriteria = batchSelectionCriteria.and(this.historyPolicy.additionalHistoryExpression(builder, builder)); |
| } |
| } |
| |
| SQLSelectStatement batchStatement = new SQLSelectStatement(); |
| |
| for (DatabaseField keyField : getReferenceKeyFields()) { |
| batchStatement.addField(builder.getTable(this.referenceTable).getField(keyField)); |
| } |
| |
| batchStatement.addField(builder.getTable(this.referenceTable).getField(this.directField)); |
| batchStatement.setWhereClause(batchSelectionCriteria); |
| batchQuery.setSQLStatement(batchStatement); |
| this.containerPolicy.addAdditionalFieldsToQuery(batchQuery, getAdditionalFieldsBaseExpression(batchQuery)); |
| |
| batchStatement.normalize(query.getSession(), descriptorToUse, clonedExpressions); |
| |
| return batchQuery; |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone and prepare the joined direct query. |
| * Since direct-collection does not build objects a nest query is not required. |
| */ |
| @Override |
| public ObjectLevelReadQuery prepareNestedJoins(JoinedAttributeManager joinManager, ObjectBuildingQuery baseQuery, AbstractSession session) { |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the value of the field from the row or a value holder on the query to obtain the object. |
| */ |
| @Override |
| protected Object valueFromRowInternalWithJoin(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, CacheKey parentCacheKey, AbstractSession executionSession, boolean isTargetProtected) throws DatabaseException { |
| ContainerPolicy policy = getContainerPolicy(); |
| Object value = policy.containerInstance(); |
| ObjectBuilder objectBuilder = this.descriptor.getObjectBuilder(); |
| // Extract the primary key of the source object, to filter only the joined rows for that object. |
| Object sourceKey = objectBuilder.extractPrimaryKeyFromRow(row, executionSession); |
| // If the query was using joining, all of the result rows by primary key will have been computed. |
| List<AbstractRecord> rows = joinManager.getDataResultsByPrimaryKey().get(sourceKey); |
| // If no 1-m rows were fetch joined, then get the value normally, |
| // this can occur with pagination where the last row may not be complete. |
| if (rows == null) { |
| return valueFromRowInternal(row, joinManager, sourceQuery, executionSession); |
| } |
| int size = rows.size(); |
| |
| if(size > 0) { |
| // A set of direct values must be maintained to avoid duplicates from multiple 1-m joins. |
| Set directValues = new HashSet(); |
| |
| ArrayList directValuesList = null; |
| ArrayList<AbstractRecord> targetRows = null; |
| boolean shouldAddAll = policy.shouldAddAll(); |
| if(shouldAddAll) { |
| directValuesList = new ArrayList(size); |
| targetRows = new ArrayList(size); |
| } |
| Converter valueConverter = getValueConverter(); |
| // indicates if collection contains null |
| boolean containsNull = false; |
| // For each rows, extract the target row and build the target object and add to the collection. |
| for (int index = 0; index < size; index++) { |
| AbstractRecord sourceRow = rows.get(index); |
| AbstractRecord targetRow = sourceRow; |
| // The field for many objects may be in the row, |
| // so build the subpartion of the row through the computed values in the query, |
| // this also helps the field indexing match. |
| targetRow = trimRowForJoin(targetRow, joinManager, executionSession); |
| // Partial object queries must select the primary key of the source and related objects. |
| // If the target joined rows in null (outerjoin) means an empty collection. |
| Object directValue = targetRow.get(this.directField); |
| if (directValue == null) { |
| if (size == 1) { |
| // A null direct value means an empty collection returned as nulls from an outerjoin. |
| return getIndirectionPolicy().valueFromRow(value); |
| } else { |
| containsNull = true; |
| } |
| } |
| // Only build/add the target object once, skip duplicates from multiple 1-m joins. |
| if (!directValues.contains(directValue)) { |
| directValues.add(directValue); |
| // Allow for value conversion. |
| if (valueConverter != null) { |
| directValue = valueConverter.convertDataValueToObjectValue(directValue, executionSession); |
| } |
| if (shouldAddAll) { |
| directValuesList.add(directValue); |
| targetRows.add(targetRow); |
| } else { |
| policy.addInto(directValue, value, executionSession, targetRow, sourceQuery, parentCacheKey, isTargetProtected); |
| } |
| } |
| } |
| if (shouldAddAll) { |
| // if collection contains a single element which is null then return an empty collection |
| if (!(containsNull && targetRows.size() == 1)) { |
| policy.addAll(directValuesList, value, executionSession, targetRows, sourceQuery, parentCacheKey, isTargetProtected); |
| } |
| } else { |
| // if collection contains a single element which is null then return an empty collection |
| if (containsNull && policy.sizeFor(value) == 1) { |
| policy.clear(value); |
| } |
| } |
| } |
| return getIndirectionPolicy().valueFromRow(value); |
| } |
| |
| /** |
| * INTERNAL: |
| * Copy of the attribute of the object. |
| * This is NOT used for unit of work but for templatizing an object. |
| */ |
| @Override |
| public void buildCopy(Object copy, Object original, CopyGroup group) { |
| Object attributeValue = getRealCollectionAttributeValueFromObject(original, group.getSession()); |
| attributeValue = getContainerPolicy().cloneFor(attributeValue); |
| // if value holder is used, then the value holder shared with original substituted for a new ValueHolder. |
| getIndirectionPolicy().reset(copy); |
| setRealAttributeValueInObject(copy, attributeValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone the element, if necessary. |
| * DirectCollections hold on to objects that do not have Descriptors |
| * (e.g. int, String). These objects do not need to be cloned, unless they use a converter - they |
| * are immutable. |
| */ |
| @Override |
| public Object buildElementClone(Object element, Object parent, CacheKey parentCacheKey, Integer refreshCascade, AbstractSession cloningSession, boolean isExisting, boolean isFromSharedCache) { |
| Object cloneValue = element; |
| if ((getValueConverter() != null) && getValueConverter().isMutable()) { |
| cloneValue = getValueConverter().convertDataValueToObjectValue(getValueConverter().convertObjectValueToDataValue(cloneValue, cloningSession), cloningSession); |
| } |
| return cloneValue; |
| } |
| |
| /** |
| * INTERNAL: |
| * In case Query By Example is used, this method builds and returns an expression that |
| * corresponds to a single attribute and it's value. |
| */ |
| @Override |
| public Expression buildExpression(Object queryObject, QueryByExamplePolicy policy, Expression expressionBuilder, Map processedObjects, AbstractSession session) { |
| if (policy.shouldValidateExample()){ |
| throw QueryException.unsupportedMappingQueryByExample(queryObject.getClass().getName(), this); |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Verifies listOrderField's table: it must be reference table. |
| * Precondition: listOrderField != null. |
| */ |
| @Override |
| protected void buildListOrderField() { |
| if(this.listOrderField.hasTableName()) { |
| if(!getReferenceTable().equals(this.listOrderField.getTable())) { |
| throw DescriptorException.listOrderFieldTableIsWrong(this.getDescriptor(), this, this.listOrderField.getTable(), getReferenceTable()); |
| } |
| } else { |
| this.listOrderField.setTable(getReferenceTable()); |
| } |
| this.listOrderField = getDescriptor().buildField(this.listOrderField, getReferenceTable()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade perform delete through mappings that require the cascade |
| */ |
| @Override |
| public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { |
| //as this mapping type references primitive objects this method does not apply |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade perform removal of orphaned private owned objects from the UnitOfWorkChangeSet |
| */ |
| @Override |
| public void cascadePerformRemovePrivateOwnedObjectFromChangeSetIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { |
| // as this mapping type references primitive objects this method does not apply |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade registerNew for Create through mappings that require the cascade |
| */ |
| @Override |
| public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { |
| //as this mapping type references primitive objects this method does not apply |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade discover and persist new objects during commit. |
| */ |
| @Override |
| public void cascadeDiscoverAndPersistUnregisteredNewObjects(Object object, Map newObjects, Map unregisteredExistingObjects, Map visitedObjects, UnitOfWorkImpl uow, Set cascadeErrors) { |
| // Direct mappings do not require any cascading. |
| } |
| |
| /** |
| * INTERNAL: |
| * The mapping clones itself to create deep copy. |
| */ |
| @Override |
| public Object clone() { |
| DirectCollectionMapping clone = (DirectCollectionMapping)super.clone(); |
| |
| clone.setSourceKeyFields(cloneFields(getSourceKeyFields())); |
| clone.setReferenceKeyFields(cloneFields(getReferenceKeyFields())); |
| |
| if(this.changeSetDeleteQuery != null) { |
| clone.changeSetDeleteQuery = (ModifyQuery)this.changeSetDeleteQuery.clone(); |
| } |
| |
| // Bug 306075 |
| if(this.changeSetDeleteNullQuery != null) { |
| clone.changeSetDeleteNullQuery = (ModifyQuery)this.changeSetDeleteNullQuery.clone(); |
| } |
| |
| if(this.deleteAtIndexQuery != null) { |
| clone.deleteAtIndexQuery = (ModifyQuery)this.deleteAtIndexQuery.clone(); |
| } |
| if(this.updateAtIndexQuery != null) { |
| clone.updateAtIndexQuery = (ModifyQuery)this.updateAtIndexQuery.clone(); |
| } |
| |
| return clone; |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to calculate the differences between two collections. |
| */ |
| @Override |
| public void compareCollectionsForChange(Object oldCollection, Object newCollection, ChangeRecord changeRecord, AbstractSession session) { |
| if(this.listOrderField != null) { |
| compareListsForChange((List)oldCollection, (List)newCollection, changeRecord, session); |
| return; |
| } |
| ContainerPolicy cp = getContainerPolicy(); |
| int numberOfNewNulls = 0; |
| |
| HashMap originalKeyValues = new HashMap(10); |
| HashMap cloneKeyValues = new HashMap(10); |
| |
| if (oldCollection != null) { |
| Object backUpIter = cp.iteratorFor(oldCollection); |
| |
| while (cp.hasNext(backUpIter)) {// Make a lookup of the objects |
| Object secondObject = cp.next(backUpIter, session); |
| |
| // For CR#2258/CR#2378 handle null values inserted in a collection. |
| if (secondObject == null) { |
| numberOfNewNulls--; |
| } else { |
| Integer count = (Integer)originalKeyValues.get(secondObject); |
| if (count == null) { |
| originalKeyValues.put(secondObject, 1); |
| } else { |
| originalKeyValues.put(secondObject, count + 1); |
| } |
| } |
| } |
| } |
| |
| // should a removal occur this is the original count of objects on the database. |
| // this value is used to determine how many objects to re-insert after the delete as a |
| // delete will delete all of the objects not just one. |
| HashMap databaseCount = (HashMap)originalKeyValues.clone(); |
| int databaseNullCount = Math.abs(numberOfNewNulls); |
| |
| if (newCollection != null) { |
| Object cloneIter = cp.iteratorFor(newCollection); |
| |
| /* The following code is used to compare objects in a direct collection. |
| Because objects in a direct collection are primitives and may be the same object |
| the following code must count the number of instances in the collection not just the |
| existence of an object. |
| */ |
| while (cp.hasNext(cloneIter)) {//Compare them with the objects from the clone |
| Object firstObject = cp.next(cloneIter, session); |
| |
| // For CR#2258/CR#2378 handle null values inserted in a collection. |
| if (firstObject == null) { |
| numberOfNewNulls++; |
| } else { |
| Integer count = (Integer)originalKeyValues.get(firstObject); |
| if (count == null) {//the object was not in the backup |
| Integer cloneCount = (Integer)cloneKeyValues.get(firstObject); |
| |
| //Add it to the additions hashtable |
| if (cloneCount == null) { |
| cloneKeyValues.put(firstObject, 1); |
| } else { |
| cloneKeyValues.put(firstObject, cloneCount + 1); |
| } |
| } else if (count == 1) { |
| //There is only one object so remove the whole reference |
| originalKeyValues.remove(firstObject); |
| } else { |
| originalKeyValues.put(firstObject, count - 1); |
| } |
| } |
| } |
| } |
| if (cloneKeyValues.isEmpty() && originalKeyValues.isEmpty() && (numberOfNewNulls == 0) && (!changeRecord.getOwner().isNew())) { |
| return; |
| } |
| ((DirectCollectionChangeRecord)changeRecord).clearChanges(); |
| ((DirectCollectionChangeRecord)changeRecord).addAdditionChange(cloneKeyValues, databaseCount); |
| ((DirectCollectionChangeRecord)changeRecord).addRemoveChange(originalKeyValues, databaseCount); |
| ((DirectCollectionChangeRecord)changeRecord).setIsDeferred(false); |
| ((DirectCollectionChangeRecord)changeRecord).setLatestCollection(null); |
| //For CR#2258, produce a changeRecord which reflects the addition and removal of null values. |
| if (numberOfNewNulls != 0) { |
| ((DirectCollectionChangeRecord)changeRecord).getCommitAddMap().put(null, databaseNullCount); |
| if (numberOfNewNulls > 0) { |
| ((DirectCollectionChangeRecord)changeRecord).addAdditionChange(null, numberOfNewNulls); |
| } else { |
| numberOfNewNulls *= -1; |
| ((DirectCollectionChangeRecord)changeRecord).addRemoveChange(null, numberOfNewNulls); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to calculate the differences between two Lists. |
| */ |
| public void compareListsForChange(List oldList, List newList, ChangeRecord changeRecord, AbstractSession session) { |
| // Maps objects (null included) in newList and oldList to an array of two Sets: |
| // the first one contains indexes of the object in oldList, the second - in newList. |
| // Contains only the objects for which the set of indexes in newList and oldList are different; |
| // only changed indexes appear in the sets (therefore the old index set and new index set don't intersect). |
| // Examples: |
| // obj was first (index 0) in oldList; first and second (indexes 0 and 1)in newList: obj -> {{}, {1}}; |
| // obj was not in oldList; first in newList: obj -> {null, {0}}; |
| // obj was first in oldList; not in newList: obj -> {{0}, null}; |
| // obj was first and second in oldList; first in newList: obj -> {{1}, {}}; |
| // Note the difference between null and empty set: |
| // empty set means there's at least one index (the same in oldList and newList - otherwise it would've been in the set); |
| // null means there's no indexes. |
| // That helps during deletion - if we know there is no remaining duplicates for the object to be removed |
| // we can delete it without checking its index (which allows delete several duplicates in one sql). |
| // Map entry sets with no new and no old indexes removed. |
| |
| int nOldSize = oldList == null ? 0 : oldList.size(); |
| int nNewSize = newList == null ? 0 : newList.size(); |
| HashMap<Object, Set[]> changedIndexes = new HashMap<>(Math.max(nOldSize, nNewSize)); |
| |
| // for each object in oldList insert all its indexes in oldList into the old indexes set corresponding to each object. |
| if (oldList != null) { |
| for(int i=0; i < nOldSize; i++) { |
| Object obj = oldList.get(i); |
| Set[] indexes = changedIndexes.get(obj); |
| if (indexes == null) { |
| // the first index found for the object. |
| indexes = new Set[]{new HashSet(), null}; |
| changedIndexes.put(obj, indexes); |
| } |
| indexes[0].add(i); |
| } |
| } |
| |
| // helper set to store objects for which entries into changedIndexes has been removed: |
| // if an entry for the object is created again, it will have an empty old indexes set (rather than null) |
| // to indicate that the object has been on the oldList, too. |
| HashSet removedFromChangedIndexes = new HashSet(); |
| HashSet dummySet = new HashSet(0); |
| |
| // for each object in newList, for each its index in newList: |
| // if the object has the same index in oldList - remove the index from old indexes set; |
| // if the object doesn't have the same index in oldList - insert the index into new indexes set. |
| if (newList != null) { |
| for(int i=0; i < nNewSize; i++) { |
| Object obj = newList.get(i); |
| Set[] indexes = changedIndexes.get(obj); |
| if (indexes == null) { |
| // the first index found for the object - or was found and removed before. |
| if(removedFromChangedIndexes.contains(obj)) { |
| // the object also exists in oldList |
| indexes = new Set[]{dummySet, new HashSet()}; |
| } else { |
| // the object does not exist in oldList |
| indexes = new Set[]{null, new HashSet()}; |
| } |
| changedIndexes.put(obj, indexes); |
| // the object doesn't have this index in oldList - add the index to new indexes set. |
| indexes[1].add(i); |
| } else { |
| if(indexes[0] == null || !indexes[0].contains(i)) { |
| // the object doesn't have this index in oldList - add the index to new indexes set. |
| if(indexes[1] == null) { |
| indexes[1] = new HashSet(); |
| } |
| indexes[1].add(i); |
| } else { |
| // the object has this index in oldList - remove the index from the old indexes set. |
| indexes[0].remove(i); |
| if(indexes[0].isEmpty()) { |
| // no old indexes left for the object. |
| if(indexes[1] == null || indexes[1].isEmpty()) { |
| // no new indexes left, too - remove the entry for the object. |
| changedIndexes.remove(obj); |
| // store the object in case it has another index on newList |
| removedFromChangedIndexes.add(obj); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| ((DirectCollectionChangeRecord)changeRecord).setChangedIndexes(changedIndexes); |
| ((DirectCollectionChangeRecord)changeRecord).setOldSize(nOldSize); |
| ((DirectCollectionChangeRecord)changeRecord).setNewSize(nNewSize); |
| } |
| |
| /** |
| * INTERNAL: |
| * This method compares the changes between two direct collections. Comparisons are made on equality |
| * not identity. |
| */ |
| @Override |
| public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession session) { |
| Object cloneAttribute = getAttributeValueFromObject(clone); |
| Object backUpAttribute = null; |
| |
| if ((cloneAttribute != null) && (!getIndirectionPolicy().objectIsInstantiated(cloneAttribute))) { |
| return null; |
| } |
| |
| Object cloneObjectCollection = getRealCollectionAttributeValueFromObject(clone, session); |
| Object backUpCollection = null; |
| |
| if (!owner.isNew()) { |
| backUpAttribute = getAttributeValueFromObject(backUp); |
| |
| if ((backUpAttribute == null) && (cloneAttribute == null)) { |
| return null; |
| } |
| |
| backUpCollection = getRealCollectionAttributeValueFromObject(backUp, session); |
| } |
| DirectCollectionChangeRecord changeRecord = new DirectCollectionChangeRecord(owner); |
| changeRecord.setAttribute(getAttributeName()); |
| changeRecord.setMapping(this); |
| if(this.listOrderField != null) { |
| changeRecord.setLatestCollection(cloneObjectCollection); |
| } |
| compareCollectionsForChange(backUpCollection, cloneObjectCollection, changeRecord, session); |
| if (changeRecord.hasChanges()) { |
| changeRecord.setOriginalCollection(backUpCollection); |
| return changeRecord; |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the attributes belonging to this mapping for the objects. |
| */ |
| @Override |
| public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) { |
| Object firstCollection = getRealCollectionAttributeValueFromObject(firstObject, session); |
| Object secondCollection = getRealCollectionAttributeValueFromObject(secondObject, session); |
| if(this.listOrderField != null) { |
| return compareLists((List)firstCollection, (List)secondCollection); |
| } |
| ContainerPolicy containerPolicy = getContainerPolicy(); |
| |
| if (containerPolicy.sizeFor(firstCollection) != containerPolicy.sizeFor(secondCollection)) { |
| return false; |
| } |
| |
| HashMap firstCounter = new HashMap(); |
| HashMap secondCounter = new HashMap(); |
| for (Object iter = containerPolicy.iteratorFor(firstCollection); |
| containerPolicy.hasNext(iter);) { |
| Object object = containerPolicy.next(iter, session); |
| if (firstCounter.containsKey(object)) { |
| int count = (Integer) firstCounter.get(object); |
| firstCounter.put(object, ++count); |
| } else { |
| firstCounter.put(object, 1); |
| } |
| } |
| for (Object iter = containerPolicy.iteratorFor(secondCollection); |
| containerPolicy.hasNext(iter);) { |
| Object object = containerPolicy.next(iter, session); |
| if (secondCounter.containsKey(object)) { |
| int count = (Integer) secondCounter.get(object); |
| secondCounter.put(object, ++count); |
| } else { |
| secondCounter.put(object, 1); |
| } |
| } |
| for (Iterator iterator = firstCounter.keySet().iterator(); iterator.hasNext();) { |
| Object object = iterator.next(); |
| |
| if (!secondCounter.containsKey(object) || (((Integer)secondCounter.get(object)).intValue() != ((Integer)firstCounter.get(object)).intValue())) { |
| // containsKey(object) will fail when the objects are arrays. |
| boolean found = false; |
| |
| for (Iterator ii = secondCounter.keySet().iterator(); ii.hasNext();) { |
| Object otherObject = ii.next(); |
| if(object == otherObject) { |
| found = true; |
| } else if(object == null || otherObject == null) { |
| found = false; |
| } else { |
| found = Helper.comparePotentialArrays(object, otherObject); |
| } |
| |
| if (found) { |
| iterator.remove(); |
| secondCounter.remove(otherObject); |
| break; |
| } |
| } |
| |
| if (!found) { |
| return false; |
| } |
| } else { |
| iterator.remove(); |
| secondCounter.remove(object); |
| } |
| } |
| if (!firstCounter.isEmpty() || !secondCounter.isEmpty()) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Compare two lists. For equality the order of the elements should be the same. |
| * Used only if listOrderField != null |
| */ |
| protected boolean compareLists(List firstList, List secondList) { |
| if (firstList.size() != secondList.size()) { |
| return false; |
| } |
| |
| int size = firstList.size(); |
| for(int i=0; i < size; i++) { |
| Object firstObject = firstList.get(i); |
| Object secondObject = secondList.get(i); |
| if(firstObject != secondObject) { |
| if(firstObject==null || secondObject==null) { |
| return false; |
| } else { |
| if(!firstObject.equals(secondObject)) { |
| return false; |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Convert all the class-name-based settings in this mapping to actual class-based |
| * settings |
| * This method is implemented by subclasses as necessary. |
| */ |
| @Override |
| public void convertClassNamesToClasses(ClassLoader classLoader) { |
| super.convertClassNamesToClasses(classLoader); |
| |
| // Tell the direct field to convert any class names (type name). |
| directField.convertClassNamesToClasses(classLoader); |
| |
| // Convert and any Converter class names. |
| convertConverterClassNamesToClasses(valueConverter, classLoader); |
| |
| // Instantiate any custom converter class |
| if (valueConverterClassName != null) { |
| Class<?> valueConverterClass; |
| Converter valueConverter; |
| |
| try { |
| if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ |
| try { |
| valueConverterClass = AccessController.doPrivileged(new PrivilegedClassForName<>(valueConverterClassName, true, classLoader)); |
| } catch (PrivilegedActionException exception) { |
| throw ValidationException.classNotFoundWhileConvertingClassNames(valueConverterClassName, exception.getException()); |
| } |
| |
| try { |
| valueConverter = (Converter) AccessController.doPrivileged(new PrivilegedNewInstanceFromClass<>(valueConverterClass)); |
| } catch (PrivilegedActionException exception) { |
| throw ValidationException.classNotFoundWhileConvertingClassNames(valueConverterClassName, exception.getException()); |
| } |
| } else { |
| valueConverterClass = PrivilegedAccessHelper.getClassForName(valueConverterClassName, true, classLoader); |
| valueConverter = (Converter) PrivilegedAccessHelper.newInstanceFromClass(valueConverterClass); |
| } |
| } catch (ClassNotFoundException exc) { |
| throw ValidationException.classNotFoundWhileConvertingClassNames(valueConverterClassName, exc); |
| } catch (Exception e) { |
| // Catches IllegalAccessException and InstantiationException |
| throw ValidationException.classNotFoundWhileConvertingClassNames(valueConverterClassName, e); |
| } |
| |
| setValueConverter(valueConverter); |
| } |
| |
| // Check if the attribute classification is set (either directly or through a type conversion converter) |
| if (attributeClassification == null) { |
| // Look for an attribute classification name |
| if (attributeClassificationName != null) { |
| try { |
| if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ |
| try { |
| attributeClassification = AccessController.doPrivileged(new PrivilegedClassForName<>(attributeClassificationName, true, classLoader)); |
| } catch (PrivilegedActionException pae) { |
| throw ValidationException.classNotFoundWhileConvertingClassNames(attributeClassificationName, pae.getException()); |
| } |
| } else { |
| attributeClassification = PrivilegedAccessHelper.getClassForName(attributeClassificationName, true, classLoader); |
| } |
| } catch (Exception exception) { |
| throw ValidationException.classNotFoundWhileConvertingClassNames(attributeClassificationName, exception); |
| } |
| } else { |
| // Still nothing, default to the type from the direct field. |
| attributeClassification = getDirectField().getType(); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Extract the source primary key value from the reference direct row. |
| * Used for batch reading, most following same order and fields as in the mapping. |
| */ |
| @Override |
| protected Object extractKeyFromTargetRow(AbstractRecord row, AbstractSession session) { |
| int size = this.referenceKeyFields.size(); |
| Object[] key = new Object[size]; |
| ConversionManager conversionManager = session.getDatasourcePlatform().getConversionManager(); |
| for (int index = 0; index < size; index++) { |
| DatabaseField relationField = this.referenceKeyFields.get(index); |
| DatabaseField sourceField = this.sourceKeyFields.get(index); |
| Object value = row.get(relationField); |
| // Must ensure the classification gets a cache hit. |
| try { |
| value = conversionManager.convertObject(value, sourceField.getType()); |
| } catch (ConversionException e) { |
| throw ConversionException.couldNotBeConverted(this, getDescriptor(), e); |
| } |
| key[index] = value; |
| } |
| return new CacheId(key); |
| } |
| |
| /** |
| * INTERNAL: |
| * Extract the primary key value from the source row. |
| * Used for batch reading, most following same order and fields as in the mapping. |
| */ |
| @Override |
| protected Object extractBatchKeyFromRow(AbstractRecord row, AbstractSession session) { |
| int size = this.sourceKeyFields.size(); |
| Object[] key = new Object[size]; |
| ConversionManager conversionManager = session.getDatasourcePlatform().getConversionManager(); |
| for (int index = 0; index < size; index++) { |
| DatabaseField field = this.sourceKeyFields.get(index); |
| Object value = row.get(field); |
| // Must ensure the classification to get a cache hit. |
| try { |
| value = conversionManager.convertObject(value, field.getType()); |
| } catch (ConversionException exception) { |
| throw ConversionException.couldNotBeConverted(this, this.descriptor, exception); |
| } |
| key[index] = value; |
| } |
| return new CacheId(key); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the selection criteria used to IN batch fetching. |
| */ |
| @Override |
| protected Expression buildBatchCriteria(ExpressionBuilder builder, ObjectLevelReadQuery query) { |
| int size = this.referenceKeyFields.size(); |
| Expression table = builder.getTable(this.referenceTable); |
| if (size > 1) { |
| // Support composite keys using nested IN. |
| List<Expression> fields = new ArrayList<>(size); |
| for (DatabaseField referenceKeyField : this.referenceKeyFields) { |
| fields.add(table.getField(referenceKeyField)); |
| } |
| return query.getSession().getPlatform().buildBatchCriteriaForComplexId(builder, fields); |
| } else { |
| return query.getSession().getPlatform().buildBatchCriteria(builder, table.getField(this.referenceKeyFields.get(0))); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare and execute the batch query and store the |
| * results for each source object in a map keyed by the |
| * mappings source keys of the source objects. |
| */ |
| @Override |
| protected void executeBatchQuery(DatabaseQuery query, CacheKey parentCacheKey, Map referenceDataByKey, AbstractSession session, AbstractRecord translationRow) { |
| // Execute query and index resulting object sets by key. |
| List<AbstractRecord> rows = (List<AbstractRecord>)session.executeQuery(query, translationRow); |
| int size = rows.size(); |
| if (this.containerPolicy.shouldAddAll()) { |
| if (size > 0) { |
| Map<Object, List[]> referenceDataAndRowsByKey = new HashMap(); |
| for (int index = 0; index < size; index++) { |
| AbstractRecord referenceRow = rows.get(index); |
| Object referenceValue = referenceRow.get(this.directField); |
| Object eachReferenceKey = extractKeyFromTargetRow(referenceRow, session); |
| |
| // Allow for value conversion. |
| if (this.valueConverter != null) { |
| referenceValue = this.valueConverter.convertDataValueToObjectValue(referenceValue, query.getSession()); |
| } |
| List[] valuesAndRows = referenceDataAndRowsByKey.get(eachReferenceKey); |
| if (valuesAndRows == null) { |
| valuesAndRows = new List[]{new ArrayList(), new ArrayList()}; |
| referenceDataAndRowsByKey.put(eachReferenceKey, valuesAndRows); |
| } |
| valuesAndRows[0].add(referenceValue); |
| valuesAndRows[1].add(referenceRow); |
| } |
| |
| Iterator<Map.Entry<Object, List[]>> iterator = referenceDataAndRowsByKey.entrySet().iterator(); |
| while (iterator.hasNext()) { |
| Map.Entry<Object, List[]> entry = iterator.next(); |
| Object eachReferenceKey = entry.getKey(); |
| List referenceValues = entry.getValue()[0]; |
| List<AbstractRecord> referenceRows = entry.getValue()[1]; |
| Object container = this.containerPolicy.containerInstance(referenceValues.size()); |
| this.containerPolicy.addAll(referenceValues, container, query.getSession(), referenceRows, (DataReadQuery)query, parentCacheKey, true); |
| referenceDataByKey.put(eachReferenceKey, container); |
| } |
| } |
| } else { |
| for (int index = 0; index < size; index++) { |
| AbstractRecord referenceRow = rows.get(index); |
| Object referenceValue = referenceRow.get(this.directField); |
| Object eachReferenceKey = extractKeyFromTargetRow(referenceRow, session); |
| |
| Object container = referenceDataByKey.get(eachReferenceKey); |
| if ((container == null) || (container == Helper.NULL_VALUE)) { |
| container = this.containerPolicy.containerInstance(); |
| referenceDataByKey.put(eachReferenceKey, container); |
| } |
| |
| // Allow for value conversion. |
| if (this.valueConverter != null) { |
| referenceValue = this.valueConverter.convertDataValueToObjectValue(referenceValue, query.getSession()); |
| } |
| this.containerPolicy.addInto(referenceValue, container, query.getSession()); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * At this point, we realize we don't have indirection; |
| * so we need to replace the reference object(s) with |
| * the corresponding object(s) from the remote session. |
| * |
| * The reference objects for a DirectCollectionMapping |
| * are primitives, so they do not need to be replaced. |
| */ |
| @Override |
| public void fixRealObjectReferences(Object object, Map<Object, ObjectDescriptor> objectInformation, Map<Object, Object> processedObjects, ObjectLevelReadQuery query, DistributedSession session) { |
| // do nothing |
| } |
| |
| |
| /** |
| * PUBLIC: |
| * Some databases do not properly support all of the base data types. For these databases, |
| * the base data type must be explicitly specified in the mapping to tell EclipseLink to force |
| * the instance variable value to that data type. |
| * @since Java Persistence API 2.0 |
| */ |
| @Override |
| public Class<?> getAttributeClassification() { |
| return attributeClassification; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the class name of the attribute type. |
| * This is only used by the MW. |
| * @since Java Persistence API 2.0 |
| */ |
| public String getAttributeClassificationName() { |
| if ((null == attributeClassificationName) && (attributeClassification != null)) { |
| attributeClassificationName = attributeClassification.getName(); |
| } |
| return attributeClassificationName; |
| } |
| |
| protected ModifyQuery getDeleteQuery() { |
| if (changeSetDeleteQuery == null) { |
| changeSetDeleteQuery = new DataModifyQuery(); |
| } |
| return changeSetDeleteQuery; |
| } |
| |
| // Bug 306075 |
| protected ModifyQuery getDeleteNullQuery() { |
| if (changeSetDeleteNullQuery == null) { |
| changeSetDeleteNullQuery = new DataModifyQuery(); |
| } |
| return changeSetDeleteNullQuery; |
| } |
| |
| protected ModifyQuery getDeleteAtIndexQuery() { |
| if (deleteAtIndexQuery == null) { |
| deleteAtIndexQuery = new DataModifyQuery(); |
| } |
| return deleteAtIndexQuery; |
| } |
| |
| protected ModifyQuery getUpdateAtIndexQuery() { |
| if (updateAtIndexQuery == null) { |
| updateAtIndexQuery = new DataModifyQuery(); |
| } |
| return updateAtIndexQuery; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the set of fields that should be selected to build this mapping's value(s). |
| * This is used by expressions to determine which fields to include in the select clause for non-object expressions. |
| */ |
| @Override |
| public Vector getSelectFields() { |
| Vector<DatabaseField> fields = new NonSynchronizedVector<>(2); |
| fields.add(getDirectField()); |
| return fields; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the table(s) that should be selected to build this mapping's value(s). |
| * This is used by expressions to determine which tables to include in the from clause for non-object expressions. |
| */ |
| @Override |
| public Vector getSelectTables() { |
| Vector<DatabaseTable> tables = new NonSynchronizedVector<>(0); |
| tables.add(getReferenceTable()); |
| return tables; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the direct field. |
| * This is the field in the direct table to store the values. |
| */ |
| public DatabaseField getDirectField() { |
| return directField; |
| } |
| |
| /** |
| * PUBLIC: |
| * Returns the name of the field name in the reference table. |
| */ |
| public String getDirectFieldName() { |
| if (getDirectField() == null) { |
| return null; |
| } |
| return getDirectField().getQualifiedName(); |
| } |
| |
| protected DataModifyQuery getInsertQuery() { |
| return insertQuery; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the join criteria stored in the mapping selection query. This criteria |
| * is used to read reference objects across the tables from the database. |
| */ |
| @Override |
| public Expression getJoinCriteria(ObjectExpression context, Expression base) { |
| if (getHistoryPolicy() != null) { |
| Expression result = super.getJoinCriteria(context, base); |
| Expression historyCriteria = getHistoryPolicy().additionalHistoryExpression(context, base); |
| if (result != null) { |
| return result.and(historyCriteria); |
| } else if (historyCriteria != null) { |
| return historyCriteria; |
| } else { |
| return null; |
| } |
| } else { |
| return super.getJoinCriteria(context, base); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * return the object on the client corresponding to the specified object. |
| * DirectCollections do not have to worry about |
| * maintaining object identity. |
| */ |
| @Override |
| public Object getObjectCorrespondingTo(Object object, DistributedSession session, Map<Object, ObjectDescriptor> objectDescriptors, Map<Object, Object> processedObjects, ObjectLevelReadQuery query) { |
| return object; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the order by expression. |
| */ |
| public List<Expression> getOrderByExpressions() { |
| return orderByExpressions; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return null if this mapping is used in an order by. |
| */ |
| @Override |
| public List<Expression> getOrderByNormalizedExpressions(Expression base) { |
| return null; |
| } |
| |
| /** |
| * PUBLIC: |
| * Allow history support on the reference table. |
| */ |
| public HistoryPolicy getHistoryPolicy() { |
| return historyPolicy; |
| } |
| |
| /** |
| * INTERNAL: |
| * Get the container policy from the selection query for this mapping. |
| */ |
| @Override |
| protected ContainerPolicy getSelectionQueryContainerPolicy() { |
| return ((DataReadQuery) getSelectionQuery()).getContainerPolicy(); |
| } |
| |
| /** |
| * INTERNAL: |
| * This cannot be used with direct collection mappings. |
| */ |
| @Override |
| public Class<?> getReferenceClass() { |
| return null; |
| } |
| |
| @Override |
| public String getReferenceClassName() { |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * There is none on direct collection. |
| */ |
| @Override |
| public ClassDescriptor getReferenceDescriptor() { |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the reference key field names associated with the mapping. |
| * These are in-order with the sourceKeyFieldNames. |
| */ |
| public Vector getReferenceKeyFieldNames() { |
| Vector fieldNames = new Vector(getReferenceKeyFields().size()); |
| for (Enumeration<DatabaseField> fieldsEnum = getReferenceKeyFields().elements(); |
| fieldsEnum.hasMoreElements();) { |
| fieldNames.addElement(fieldsEnum.nextElement().getQualifiedName()); |
| } |
| |
| return fieldNames; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the reference key fields. |
| */ |
| public Vector<DatabaseField> getReferenceKeyFields() { |
| return referenceKeyFields; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the direct table. |
| * This is the table to store the values. |
| */ |
| public DatabaseTable getReferenceTable() { |
| return referenceTable; |
| } |
| |
| /** |
| * PUBLIC: |
| * Returns the name of the reference table |
| */ |
| public String getReferenceTableName() { |
| if (getReferenceTable() == null) { |
| return null; |
| } |
| return getReferenceTable().getName(); |
| } |
| |
| //This method is added to include table qualifier. |
| |
| /** |
| * PUBLIC: |
| * Returns the qualified name of the reference table. |
| */ |
| public String getReferenceTableQualifiedName() {//CR#2407 |
| if (getReferenceTable() == null) { |
| return null; |
| } |
| return getReferenceTable().getQualifiedName(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the relationshipPartner mapping for this bi-directional mapping. If the relationshipPartner is null then |
| * this is a uni-directional mapping. |
| * DirectCollectionMapping can not be part of a bi-directional mapping |
| */ |
| @Override |
| public DatabaseMapping getRelationshipPartner() { |
| return null; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the source key field names associated with the mapping. |
| * These are in-order with the referenceKeyFieldNames. |
| */ |
| public Vector getSourceKeyFieldNames() { |
| Vector fieldNames = new Vector(getSourceKeyFields().size()); |
| for (Enumeration<DatabaseField> fieldsEnum = getSourceKeyFields().elements(); |
| fieldsEnum.hasMoreElements();) { |
| fieldNames.addElement(fieldsEnum.nextElement().getQualifiedName()); |
| } |
| |
| return fieldNames; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the source key fields. |
| */ |
| public Vector<DatabaseField> getSourceKeyFields() { |
| return sourceKeyFields; |
| } |
| |
| protected boolean hasCustomDeleteQuery() { |
| return hasCustomDeleteQuery; |
| } |
| |
| protected boolean hasCustomInsertQuery() { |
| return hasCustomInsertQuery; |
| } |
| |
| protected boolean hasCustomDeleteAtIndexQuery() { |
| return hasCustomDeleteAtIndexQuery; |
| } |
| |
| protected boolean hasCustomUpdateAtIndexQuery() { |
| return hasCustomUpdateAtIndexQuery; |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether the mapping (or at least one of its nested mappings, at any nested depth) |
| * references an entity. |
| * To return true the mapping (or nested mapping) should be ForeignReferenceMapping with non-null and non-aggregate reference descriptor. |
| */ |
| @Override |
| public boolean hasNestedIdentityReference() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Initialize and validate the mapping properties. |
| */ |
| @Override |
| public void initialize(AbstractSession session) throws DescriptorException { |
| if (session.hasBroker()) { |
| if (getInsertQuery().hasSessionName()) { |
| // substitute session that owns the mapping for the session that owns reference table. |
| session = session.getBroker().getSessionForName(getInsertQuery().getSessionName()); |
| } |
| } |
| |
| if (isKeyForSourceSpecified()) { |
| initializeSourceKeys(session); |
| } else { |
| initializeSourceKeysWithDefaults(session); |
| } |
| |
| initializeReferenceTable(session); |
| initializeReferenceKeys(session); |
| initializeDirectField(session); |
| |
| if (getReferenceTable().getName().indexOf(' ') != -1) { |
| //table names contains a space so needs to be quoted. |
| String beginQuote = session.getDatasourcePlatform().getStartDelimiter(); |
| String endQuote = session.getDatasourcePlatform().getEndDelimiter(); |
| //Ensure this tablename hasn't already been quoted. |
| if (getReferenceTable().getName().indexOf(beginQuote) == -1) { |
| getReferenceTable().setName(beginQuote + getReferenceTable().getName() + endQuote); |
| } |
| } |
| if (this.listOrderField != null) { |
| this.initializeListOrderField(session); |
| } |
| getContainerPolicy().initialize(session, this.referenceTable); |
| |
| // Initialize the value converter sooner since it likely will finish |
| // configuring field and attribute classifications. |
| if (getValueConverter() != null) { |
| getValueConverter().initialize(this, session); |
| } |
| |
| if (!hasCustomSelectionQuery()){ |
| initOrRebuildSelectQuery(); |
| getSelectionQuery().setName(getAttributeName()); |
| |
| if (shouldInitializeSelectionCriteria()) { |
| initializeSelectionCriteria(session); |
| initializeSelectionStatement(session); |
| } |
| } |
| if (!getSelectionQuery().hasSessionName()) { |
| getSelectionQuery().setSessionName(session.getName()); |
| } |
| if (getSelectionQuery().getPartitioningPolicy() == null) { |
| getSelectionQuery().setPartitioningPolicy(getPartitioningPolicy()); |
| } |
| getSelectionQuery().setSourceMapping(this); |
| if ((getValueConverter() != null) && (getSelectionQuery() instanceof DirectReadQuery)) { |
| ((DirectReadQuery)getSelectionQuery()).setValueConverter(getValueConverter()); |
| } |
| initializeDeleteAllQuery(session); |
| initializeDeleteQuery(session); |
| initializeDeleteNullQuery(session); // Bug 306075 |
| initializeInsertQuery(session); |
| initializeDeleteAtIndexQuery(session); |
| initializeUpdateAtIndexQuery(session); |
| if (getHistoryPolicy() != null) { |
| getHistoryPolicy().initialize(session); |
| } |
| super.initialize(session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Initializes listOrderField. |
| * Precondition: listOrderField != null. |
| */ |
| @Override |
| protected void initializeListOrderField(AbstractSession session) { |
| // This method is called twice. The second call (by CollectionMapping.initialize) should be ignored because initialization has been already done. |
| if(!getContainerPolicy().isOrderedListPolicy() || ((OrderedListContainerPolicy)getContainerPolicy()).getListOrderField() == null) { |
| super.initializeListOrderField(session); |
| } |
| } |
| |
| /** |
| * Initialize delete all query. This query is used to delete the collection of objects from the |
| * reference table. |
| */ |
| protected void initializeDeleteAllQuery(AbstractSession session) { |
| if (!getDeleteAllQuery().hasSessionName()) { |
| getDeleteAllQuery().setSessionName(session.getName()); |
| } |
| if (getDeleteAllQuery().getPartitioningPolicy() == null) { |
| getDeleteAllQuery().setPartitioningPolicy(getPartitioningPolicy()); |
| } |
| |
| if (hasCustomDeleteAllQuery()) { |
| return; |
| } |
| |
| Expression expression = null; |
| Expression subExp1; |
| Expression subExp2; |
| Expression subExpression; |
| Expression builder = new ExpressionBuilder(); |
| SQLDeleteStatement statement = new SQLDeleteStatement(); |
| |
| // Construct an expression to delete from the relation table. |
| for (int index = 0; index < getReferenceKeyFields().size(); index++) { |
| DatabaseField referenceKey = getReferenceKeyFields().elementAt(index); |
| DatabaseField sourceKey = getSourceKeyFields().elementAt(index); |
| |
| subExp1 = builder.getField(referenceKey); |
| subExp2 = builder.getParameter(sourceKey); |
| subExpression = subExp1.equal(subExp2); |
| |
| if (expression == null) { |
| expression = subExpression; |
| } else { |
| expression = expression.and(subExpression); |
| } |
| } |
| |
| statement.setWhereClause(expression); |
| statement.setTable(getReferenceTable()); |
| getDeleteAllQuery().setSQLStatement(statement); |
| } |
| |
| protected void initializeDeleteQuery(AbstractSession session) { |
| if (!getDeleteQuery().hasSessionName()) { |
| getDeleteQuery().setSessionName(session.getName()); |
| } |
| if (getDeleteQuery().getPartitioningPolicy() == null) { |
| getDeleteQuery().setPartitioningPolicy(getPartitioningPolicy()); |
| } |
| |
| if (hasCustomDeleteQuery()) { |
| return; |
| } |
| |
| SQLDeleteStatement statement = new SQLDeleteStatement(); |
| ExpressionBuilder builder = new ExpressionBuilder(); |
| Expression expression = createWhereClauseForDeleteQuery(builder); |
| statement.setWhereClause(expression); |
| statement.setTable(getReferenceTable()); |
| getDeleteQuery().setSQLStatement(statement); |
| } |
| |
| // Bug 306075 - for deleting a null value from a collection |
| protected void initializeDeleteNullQuery(AbstractSession session) { |
| if (!getDeleteNullQuery().hasSessionName()) { |
| getDeleteNullQuery().setSessionName(session.getName()); |
| } |
| if (getDeleteNullQuery().getPartitioningPolicy() == null) { |
| getDeleteNullQuery().setPartitioningPolicy(getPartitioningPolicy()); |
| } |
| |
| SQLDeleteStatement statement = new SQLDeleteStatement(); |
| ExpressionBuilder builder = new ExpressionBuilder(); |
| Expression expression = createWhereClauseForDeleteNullQuery(builder); |
| statement.setWhereClause(expression); |
| statement.setTable(getReferenceTable()); |
| getDeleteNullQuery().setSQLStatement(statement); |
| } |
| |
| protected void initializeDeleteAtIndexQuery(AbstractSession session) { |
| if (!getDeleteAtIndexQuery().hasSessionName()) { |
| getDeleteAtIndexQuery().setSessionName(session.getName()); |
| } |
| if (getDeleteAtIndexQuery().getPartitioningPolicy() == null) { |
| getDeleteAtIndexQuery().setPartitioningPolicy(getPartitioningPolicy()); |
| } |
| |
| if (hasCustomDeleteAtIndexQuery()) { |
| return; |
| } |
| |
| SQLDeleteStatement statement = new SQLDeleteStatement(); |
| ExpressionBuilder builder = new ExpressionBuilder(); |
| Expression expression = createWhereClauseForDeleteQuery(builder); |
| expression = expression.and(builder.getField(this.listOrderField).equal(builder.getParameter(this.listOrderField))); |
| statement.setWhereClause(expression); |
| statement.setTable(getReferenceTable()); |
| getDeleteAtIndexQuery().setSQLStatement(statement); |
| } |
| |
| protected void initializeUpdateAtIndexQuery(AbstractSession session) { |
| if (!getUpdateAtIndexQuery().hasSessionName()) { |
| getUpdateAtIndexQuery().setSessionName(session.getName()); |
| } |
| if (getUpdateAtIndexQuery().getPartitioningPolicy() == null) { |
| getUpdateAtIndexQuery().setPartitioningPolicy(getPartitioningPolicy()); |
| } |
| if (hasCustomUpdateAtIndexQuery()) { |
| return; |
| } |
| |
| SQLUpdateStatement statement = new SQLUpdateStatement(); |
| ExpressionBuilder builder = new ExpressionBuilder(); |
| Expression expression = createWhereClauseForDeleteQuery(builder); |
| expression = expression.and(builder.getField(this.listOrderField).equal(builder.getParameter(this.listOrderField))); |
| statement.setWhereClause(expression); |
| statement.setTable(getReferenceTable()); |
| AbstractRecord modifyRow = new DatabaseRecord(); |
| modifyRow.add(this.listOrderField, null); |
| statement.setModifyRow(modifyRow); |
| getUpdateAtIndexQuery().setSQLStatement(statement); |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether getListOrderFieldExpression method should create field expression on table expression. |
| */ |
| @Override |
| public boolean shouldUseListOrderFieldTableExpression() { |
| return true; |
| } |
| |
| protected Expression createWhereClauseForDeleteQuery(ExpressionBuilder builder) { |
| Expression directExp = builder.getField(getDirectField()).equal(builder.getParameter(getDirectField())); |
| Expression expression = null; |
| |
| // Construct an expression to delete from the relation table. |
| for (int index = 0; index < getReferenceKeyFields().size(); index++) { |
| DatabaseField referenceKey = getReferenceKeyFields().get(index); |
| DatabaseField sourceKey = getSourceKeyFields().get(index); |
| |
| Expression subExp1 = builder.getField(referenceKey); |
| Expression subExp2 = builder.getParameter(sourceKey); |
| Expression subExpression = subExp1.equal(subExp2); |
| |
| expression = subExpression.and(expression); |
| } |
| expression = expression.and(directExp); |
| return expression; |
| } |
| |
| // Bug 306075 - for deleting a null value from a collection |
| protected Expression createWhereClauseForDeleteNullQuery(ExpressionBuilder builder) { |
| Expression directExp = builder.getField(getDirectField()).isNull(); |
| Expression expression = null; |
| |
| // Construct an expression to delete from the relation table. |
| for (int index = 0; index < getReferenceKeyFields().size(); index++) { |
| DatabaseField referenceKey = getReferenceKeyFields().get(index); |
| DatabaseField sourceKey = getSourceKeyFields().get(index); |
| |
| Expression subExp1 = builder.getField(referenceKey); |
| Expression subExp2 = builder.getParameter(sourceKey); |
| Expression subExpression = subExp1.equal(subExp2); |
| |
| expression = subExpression.and(expression); |
| } |
| expression = expression.and(directExp); |
| return expression; |
| } |
| |
| /** |
| * The field name on the reference table is initialized and cached. |
| */ |
| protected void initializeDirectField(AbstractSession session) throws DescriptorException { |
| if (getDirectField() == null) { |
| throw DescriptorException.directFieldNameNotSet(this); |
| } |
| |
| getDirectField().setTable(getReferenceTable()); |
| getDirectField().setIndex(0); |
| } |
| |
| /** |
| * Initialize insert query. This query is used to insert the collection of objects into the |
| * reference table. |
| */ |
| protected void initializeInsertQuery(AbstractSession session) { |
| if (!getInsertQuery().hasSessionName()) { |
| getInsertQuery().setSessionName(session.getName()); |
| } |
| if (getInsertQuery().getPartitioningPolicy() == null) { |
| getInsertQuery().setPartitioningPolicy(getPartitioningPolicy()); |
| } |
| |
| if (hasCustomInsertQuery()) { |
| return; |
| } |
| |
| SQLInsertStatement statement = new SQLInsertStatement(); |
| statement.setTable(getReferenceTable()); |
| AbstractRecord directRow = new DatabaseRecord(); |
| for (Enumeration<DatabaseField> referenceEnum = getReferenceKeyFields().elements(); |
| referenceEnum.hasMoreElements();) { |
| directRow.put(referenceEnum.nextElement(), null); |
| } |
| directRow.put(getDirectField(), null); |
| if(listOrderField != null) { |
| directRow.put(listOrderField, null); |
| } |
| statement.setModifyRow(directRow); |
| getInsertQuery().setSQLStatement(statement); |
| getInsertQuery().setModifyRow(directRow); |
| } |
| |
| /** |
| * There is no reference descriptor |
| */ |
| @Override |
| protected void initializeReferenceDescriptor(AbstractSession session) { |
| // no-op. |
| } |
| |
| /** |
| * The reference keys on the reference table are initialized |
| */ |
| protected void initializeReferenceKeys(AbstractSession session) throws DescriptorException { |
| if (getReferenceKeyFields().size() == 0) { |
| throw DescriptorException.noReferenceKeyIsSpecified(this); |
| } |
| |
| for (Enumeration<DatabaseField> referenceEnum = getReferenceKeyFields().elements(); referenceEnum.hasMoreElements();) { |
| DatabaseField field = referenceEnum.nextElement(); |
| |
| // Update the field first if the mapping is on a table per tenant entity. |
| if (getDescriptor().hasTablePerMultitenantPolicy()) { |
| field.setTable(((TablePerMultitenantPolicy) getDescriptor().getMultitenantPolicy()).getTable(field.getTable())); |
| } |
| |
| if (field.hasTableName() && (!(field.getTableName().equals(getReferenceTable().getName())))) { |
| throw DescriptorException.referenceKeyFieldNotProperlySpecified(field, this); |
| } |
| field.setTable(getReferenceTable()); |
| } |
| } |
| |
| /** |
| * Set the table qualifier on the reference table if required |
| */ |
| protected void initializeReferenceTable(AbstractSession session) throws DescriptorException { |
| Platform platform = session.getDatasourcePlatform(); |
| |
| if (getReferenceTable() == null) { |
| throw DescriptorException.referenceTableNotSpecified(this); |
| } |
| |
| if (platform.getTableQualifier().length() > 0) { |
| if (getReferenceTable().getTableQualifier().length() == 0) { |
| getReferenceTable().setTableQualifier(platform.getTableQualifier()); |
| } |
| } |
| } |
| |
| protected void initializeSelectionCriteria(AbstractSession session) { |
| Expression criteria = null; |
| ExpressionBuilder base = new ExpressionBuilder(); |
| TableExpression table = (TableExpression)base.getTable(getReferenceTable()); |
| Iterator<DatabaseField> referenceKeys = getReferenceKeyFields().iterator(); |
| Iterator<DatabaseField> sourceKeys = getSourceKeyFields().iterator(); |
| while (referenceKeys.hasNext()) { |
| DatabaseField referenceKey = referenceKeys.next(); |
| DatabaseField sourceKey = sourceKeys.next(); |
| Expression expression = table.getField(referenceKey).equal(base.getParameter(sourceKey)); |
| if (criteria == null) { |
| criteria = expression; |
| } else { |
| criteria = expression.and(criteria); |
| } |
| } |
| setSelectionCriteria(criteria); |
| } |
| |
| /** |
| * The selection query is initialized |
| */ |
| @Override |
| protected void initializeSelectionQuery(AbstractSession session) { |
| // Nothing required. |
| } |
| |
| protected void initializeSelectionStatement(AbstractSession session) { |
| SQLSelectStatement statement = new SQLSelectStatement(); |
| statement.addTable(getReferenceTable()); |
| statement.addField(getDirectField().clone()); |
| statement.setWhereClause(getSelectionCriteria()); |
| statement.setOrderByExpressions(orderByExpressions); |
| getSelectionQuery().setSQLStatement(statement); |
| getContainerPolicy().addAdditionalFieldsToQuery(selectionQuery, getAdditionalFieldsBaseExpression(getSelectionQuery())); |
| statement.normalize(session, null); |
| } |
| |
| /** |
| * The source keys are initialized |
| */ |
| protected void initializeSourceKeys(AbstractSession session) { |
| for (int index = 0; index < getSourceKeyFields().size(); index++) { |
| DatabaseField field = getDescriptor().buildField(getSourceKeyFields().get(index)); |
| if (usesIndirection()) { |
| field.setKeepInRow(true); |
| } |
| getSourceKeyFields().set(index, field); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * If a user does not specify the source key then the primary keys of the source table are used. |
| */ |
| protected void initializeSourceKeysWithDefaults(AbstractSession session) { |
| List<DatabaseField> primaryKeyFields = getDescriptor().getPrimaryKeyFields(); |
| for (int index = 0; index < primaryKeyFields.size(); index++) { |
| DatabaseField field = primaryKeyFields.get(index); |
| if (usesIndirection()) { |
| field.setKeepInRow(true); |
| } |
| getSourceKeyFields().addElement(field); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the base expression to use for adding fields to the query. |
| * This is the reference table. |
| */ |
| @Override |
| protected Expression getAdditionalFieldsBaseExpression(ReadQuery query) { |
| if (query.isReadAllQuery()) { |
| return ((ReadAllQuery)query).getExpressionBuilder(); |
| } else { |
| return query.getSQLStatement().getBuilder().getTable(getReferenceTable()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isDirectCollectionMapping() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isElementCollectionMapping() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if this mapping support joining. |
| */ |
| @Override |
| public boolean isJoiningSupported() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Checks if source and target keys are mentioned by the user or not. |
| */ |
| protected boolean isKeyForSourceSpecified() { |
| return !getSourceKeyFields().isEmpty(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether this mapping should be traversed when we are locking |
| */ |
| @Override |
| public boolean isLockableMapping(){ |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isOwned(){ |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Iterate on the attribute value. |
| * The value holder has already been processed. |
| * PERF: Avoid iteration if not required. |
| */ |
| @Override |
| public void iterateOnRealAttributeValue(DescriptorIterator iterator, Object realAttributeValue) { |
| if (iterator.shouldIterateOnPrimitives()) { |
| super.iterateOnRealAttributeValue(iterator, realAttributeValue); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Iterate on the specified element. |
| */ |
| @Override |
| public void iterateOnElement(DescriptorIterator iterator, Object element) { |
| iterator.iteratePrimitiveForMapping(element, this); |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes from the source to the target object. |
| * Because this is a collection mapping, values are added to or removed from the |
| * collection based on the changeset |
| */ |
| @Override |
| public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| if (this.descriptor.getCachePolicy().isProtectedIsolation()&& !this.isCacheable && !targetSession.isProtectedSession()){ |
| setAttributeValueInObject(target, this.indirectionPolicy.buildIndirectObject(new ValueHolder<>(null))); |
| return; |
| } |
| ContainerPolicy containerPolicy = getContainerPolicy(); |
| Object valueOfTarget = null; |
| AbstractSession session = mergeManager.getSession(); |
| |
| DirectCollectionChangeRecord directCollectionChangeRecord = (DirectCollectionChangeRecord) changeRecord; |
| |
| //Check to see if the target has an instantiated collection |
| if ((isAttributeValueInstantiated(target)) && (!changeRecord.getOwner().isNew())) { |
| if (isSynchronizeOnMerge) { |
| valueOfTarget = getRealCollectionAttributeValueFromObject(target, session); |
| } else { |
| valueOfTarget = containerPolicy.cloneFor(getRealCollectionAttributeValueFromObject(target, session)); |
| } |
| } else { |
| //if not create an instance of the collection |
| valueOfTarget = containerPolicy.containerInstance(directCollectionChangeRecord.getAddObjectMap().size()); |
| } |
| if (!isAttributeValueInstantiated(target)) { |
| if (mergeManager.shouldMergeChangesIntoDistributedCache()) { |
| return; |
| } |
| for (Object iterator = containerPolicy.iteratorFor(getRealCollectionAttributeValueFromObject(source, session)); |
| containerPolicy.hasNext(iterator);) { |
| containerPolicy.addInto(containerPolicy.next(iterator, session), valueOfTarget, session); |
| } |
| } else { |
| Object synchronizationTarget = valueOfTarget; |
| // For indirect containers the delegate must be synchronized on, |
| // not the wrapper as the clone synchs on the delegate, see bug#5685287. |
| if (valueOfTarget instanceof IndirectCollection) { |
| synchronizationTarget = ((IndirectCollection)valueOfTarget).getDelegateObject(); |
| if (((DirectCollectionChangeRecord)changeRecord).orderHasBeenRepaired() && (valueOfTarget instanceof IndirectList)) { |
| ((IndirectList)valueOfTarget).setIsListOrderBrokenInDb(false); |
| } |
| } |
| if (isSynchronizeOnMerge) { |
| synchronized(synchronizationTarget) { |
| mergeAddRemoveChanges(valueOfTarget, synchronizationTarget, directCollectionChangeRecord, mergeManager, session); |
| } |
| } else { |
| mergeAddRemoveChanges(valueOfTarget, synchronizationTarget, directCollectionChangeRecord, mergeManager, session); |
| } |
| } |
| setRealAttributeValueInObject(target, valueOfTarget); |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes by adding and removing from the change record to the |
| * target object, and its delegate object if instance of IndirectCollection. |
| * It will also reorder the collection if required. |
| */ |
| protected void mergeAddRemoveChanges(Object valueOfTarget, Object delegateTarget, DirectCollectionChangeRecord changeRecord, MergeManager mergeManager, AbstractSession session) { |
| //collect the changes into a vector |
| HashMap addObjects = changeRecord.getAddObjectMap(); |
| HashMap removeObjects = changeRecord.getRemoveObjectMap(); |
| |
| // Next iterate over the changes and add them to the container |
| for (Iterator iterator = addObjects.keySet().iterator(); iterator.hasNext();) { |
| Object object = iterator.next(); |
| int objectCount = (Integer) addObjects.get(object); |
| for (int i = 0; i < objectCount; ++i) { |
| if (mergeManager.shouldMergeChangesIntoDistributedCache()) { |
| //bug#4458089 and 4544532- check if collection contains new item before adding during merge into distributed cache |
| if (!containerPolicy.contains(object, valueOfTarget, session)) { |
| containerPolicy.addInto(object, valueOfTarget, session); |
| } |
| } else { |
| containerPolicy.addInto(object, valueOfTarget, session); |
| } |
| } |
| } |
| for (Iterator iterator = removeObjects.keySet().iterator(); iterator.hasNext();) { |
| Object object = iterator.next(); |
| int objectCount = (Integer) removeObjects.get(object); |
| for (int i = 0; i < objectCount; ++i) { |
| containerPolicy.removeFrom(object, valueOfTarget, session); |
| } |
| } |
| if(this.listOrderField != null && changeRecord.getChangedIndexes() == null) { |
| this.compareListsForChange((List)changeRecord.getOriginalCollection(), (List)changeRecord.getLatestCollection(), changeRecord, session); |
| } |
| |
| if(changeRecord.getChangedIndexes() != null) { |
| int oldSize = changeRecord.getOldSize(); |
| int newSize = changeRecord.getNewSize(); |
| int delta = newSize - oldSize; |
| Object[] newTail = null; |
| if(delta > 0) { |
| newTail = new Object[delta]; |
| } |
| Iterator<Map.Entry<Object, Set[]>> it = changeRecord.getChangedIndexes().entrySet().iterator(); |
| while(it.hasNext()) { |
| Map.Entry<Object, Set[]> entry = it.next(); |
| Object value = entry.getKey(); |
| Set[] indexes = entry.getValue(); |
| Set indexesAfter = indexes[1]; |
| if(indexesAfter != null) { |
| Iterator<Integer> itIndexesAfter = indexesAfter.iterator(); |
| while(itIndexesAfter.hasNext()) { |
| int index = itIndexesAfter.next(); |
| if(index < oldSize) { |
| ((List)delegateTarget).set(index, value); |
| } else { |
| newTail[index - oldSize] = value; |
| } |
| } |
| } |
| } |
| if(delta > 0) { |
| for(int i=0; i < delta; i++) { |
| ((List)delegateTarget).add(newTail[i]); |
| } |
| } else if(delta < 0) { |
| for(int i=oldSize -1 ; i >= newSize; i--) { |
| ((List)delegateTarget).remove(i); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes from the source to the target object. |
| */ |
| @Override |
| public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| if (this.descriptor.getCachePolicy().isProtectedIsolation() && !this.isCacheable && !targetSession.isProtectedSession()){ |
| setAttributeValueInObject(target, this.indirectionPolicy.buildIndirectObject(new ValueHolder<>(null))); |
| return; |
| } |
| if (isTargetUnInitialized) { |
| // This will happen if the target object was removed from the cache before the commit was attempted |
| if (mergeManager.shouldMergeWorkingCopyIntoOriginal() && (!isAttributeValueInstantiated(source))) { |
| setAttributeValueInObject(target, getIndirectionPolicy().getOriginalIndirectionObject(getAttributeValueFromObject(source), targetSession)); |
| return; |
| } |
| } |
| if (!shouldMergeCascadeReference(mergeManager)) { |
| // This is only going to happen on mergeClone, and we should not attempt to merge the reference |
| return; |
| } |
| if (mergeManager.shouldRefreshRemoteObject() && usesIndirection()) { |
| mergeRemoteValueHolder(target, source, mergeManager); |
| return; |
| } |
| if (mergeManager.isForRefresh()) { |
| if (!isAttributeValueInstantiated(target)) { |
| // This will occur when the clone's value has not been instantiated yet and we do not need |
| // the refresh that attribute |
| return; |
| } |
| } else if (!isAttributeValueInstantiatedOrChanged(source)) { |
| // I am merging from a clone into an original. No need to do merge if the attribute was never |
| // modified |
| return; |
| } |
| |
| ContainerPolicy containerPolicy = getContainerPolicy(); |
| Object valueOfSource = getRealCollectionAttributeValueFromObject(source, mergeManager.getSession()); |
| |
| // trigger instantiation of target attribute |
| Object valueOfTarget = getRealCollectionAttributeValueFromObject(target, mergeManager.getSession()); |
| Object newContainer = containerPolicy.containerInstance(containerPolicy.sizeFor(valueOfSource)); |
| boolean fireCollectionChangeEvents = false; |
| boolean firePropertyChangeEvent = false; |
| ObjectChangeListener listener = null; |
| if ((this.descriptor.getObjectChangePolicy().isObjectChangeTrackingPolicy()) && (target instanceof ChangeTracker) && (((ChangeTracker)target)._persistence_getPropertyChangeListener() != null)) { |
| listener = (ObjectChangeListener)((ChangeTracker)target)._persistence_getPropertyChangeListener(); |
| if(this.listOrderField == null) { |
| fireCollectionChangeEvents = true; |
| //Collections may not be indirect list or may have been replaced with user collection. |
| Object iterator = containerPolicy.iteratorFor(valueOfTarget); |
| Integer zero = 0;//remove does not seem to use index. |
| while (containerPolicy.hasNext(iterator)) { |
| // Bug304251: let the containerPolicy build the proper remove CollectionChangeEvent |
| CollectionChangeEvent event = containerPolicy.createChangeEvent(target, getAttributeName(), valueOfTarget, containerPolicy.next(iterator, mergeManager.getSession()), CollectionChangeEvent.REMOVE, zero, false); |
| listener.internalPropertyChange(event); |
| } |
| if (newContainer instanceof ChangeTracker) { |
| ((ChangeTracker)newContainer)._persistence_setPropertyChangeListener(((ChangeTracker)target)._persistence_getPropertyChangeListener()); |
| } |
| if (valueOfTarget instanceof ChangeTracker) { |
| ((ChangeTracker)valueOfTarget)._persistence_setPropertyChangeListener(null);//remove listener |
| } |
| } else { |
| firePropertyChangeEvent = true; |
| } |
| } |
| Object originalValueOfTarget = valueOfTarget; |
| valueOfTarget = newContainer; |
| int i = 0; |
| for (Object sourceValuesIterator = containerPolicy.iteratorFor(valueOfSource); |
| containerPolicy.hasNext(sourceValuesIterator);) { |
| Object sourceValue = containerPolicy.next(sourceValuesIterator, mergeManager.getSession()); |
| if (fireCollectionChangeEvents) { |
| // Bug304251: let the containerPolicy build the proper remove CollectionChangeEvent |
| CollectionChangeEvent event = containerPolicy.createChangeEvent(target, getAttributeName(), valueOfTarget, sourceValue, CollectionChangeEvent.ADD, i, false); |
| listener.internalPropertyChange(event); |
| } |
| containerPolicy.addInto(sourceValue, valueOfTarget, mergeManager.getSession()); |
| i++; |
| } |
| if (fireCollectionChangeEvents && (this.descriptor.getObjectChangePolicy().isAttributeChangeTrackingPolicy())) { |
| // check that there were changes, if not then remove the record. |
| ObjectChangeSet changeSet = ((AttributeChangeListener)((ChangeTracker)target)._persistence_getPropertyChangeListener()).getObjectChangeSet(); |
| if (changeSet != null) { |
| DirectCollectionChangeRecord changeRecord = (DirectCollectionChangeRecord)changeSet.getChangesForAttributeNamed(getAttributeName()); |
| if (changeRecord != null) { |
| if (!changeRecord.isDeferred()) { |
| if (!changeRecord.hasChanges()) { |
| changeSet.removeChange(getAttributeName()); |
| } |
| } else { |
| // Must reset the latest collection. |
| changeRecord.setLatestCollection(valueOfTarget); |
| } |
| } |
| } |
| } |
| |
| if(firePropertyChangeEvent) { |
| ((ObjectChangeListener)((ChangeTracker)target)._persistence_getPropertyChangeListener()).internalPropertyChange(new PropertyChangeEvent(target, getAttributeName(), originalValueOfTarget, valueOfTarget)); |
| if (valueOfTarget instanceof ChangeTracker) { |
| ((ChangeTracker)valueOfTarget)._persistence_setPropertyChangeListener(((ChangeTracker)target)._persistence_getPropertyChangeListener()); |
| } |
| if (originalValueOfTarget instanceof ChangeTracker) { |
| ((ChangeTracker)originalValueOfTarget)._persistence_setPropertyChangeListener(null);//remove listener |
| } |
| } |
| |
| // Must re-set variable to allow for set method to re-morph changes if the collection is not being stored directly. |
| setRealAttributeValueInObject(target, valueOfTarget); |
| } |
| |
| /** |
| * INTERNAL: |
| * Perform the commit event. |
| * This is used in the uow to delay data modifications. |
| */ |
| @Override |
| public void performDataModificationEvent(Object[] event, AbstractSession session) throws DatabaseException, DescriptorException { |
| // Hey I might actually want to use an inner class here... ok array for now. |
| if (event[0] == Delete) { |
| session.executeQuery((DataModifyQuery)event[1], (AbstractRecord)event[2]); |
| if ((getHistoryPolicy() != null) && getHistoryPolicy().shouldHandleWrites()) { |
| getHistoryPolicy().mappingLogicalDelete((DataModifyQuery)event[1], (AbstractRecord)event[2], session); |
| } |
| } else if (event[0] == Insert) { |
| session.executeQuery((DataModifyQuery)event[1], (AbstractRecord)event[2]); |
| if ((getHistoryPolicy() != null) && getHistoryPolicy().shouldHandleWrites()) { |
| getHistoryPolicy().mappingLogicalInsert((DataModifyQuery)event[1], (AbstractRecord)event[2], session); |
| } |
| } else if (event[0] == DeleteAll) { |
| preDelete((DeleteObjectQuery)event[1]); |
| } else if (event[0] == DeleteAtIndex) { |
| session.executeQuery((DataModifyQuery)event[1], (AbstractRecord)event[2]); |
| } else if (event[0] == UpdateAtIndex) { |
| DataModifyQuery updateAtIndexQuery = (DataModifyQuery)((DataModifyQuery)event[1]).clone(); |
| updateAtIndexQuery.setModifyRow((AbstractRecord)event[3]); |
| updateAtIndexQuery.setHasModifyRow(true); |
| updateAtIndexQuery.setIsExecutionClone(true); |
| session.executeQuery(updateAtIndexQuery, (AbstractRecord)event[2]); |
| } else { |
| throw DescriptorException.invalidDataModificationEventCode(event[0], this); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Overridden by mappings that require additional processing of the change record after the record has been calculated. |
| */ |
| @Override |
| public void postCalculateChanges(org.eclipse.persistence.sessions.changesets.ChangeRecord changeRecord, UnitOfWorkImpl uow) { |
| //no -op for this collection type |
| } |
| |
| /** |
| * INTERNAL: |
| * Insert the private owned object. |
| */ |
| @Override |
| public void postInsert(WriteObjectQuery query) throws DatabaseException { |
| Object objects; |
| AbstractRecord databaseRow = new DatabaseRecord(); |
| |
| if (isReadOnly()) { |
| return; |
| } |
| |
| objects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession()); |
| ContainerPolicy containerPolicy = getContainerPolicy(); |
| if (containerPolicy.isEmpty(objects)) { |
| return; |
| } |
| |
| prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getDescriptor(), query.getSession()); |
| // Extract primary key and value from the source. |
| for (int index = 0; index < getReferenceKeyFields().size(); index++) { |
| DatabaseField referenceKey = getReferenceKeyFields().get(index); |
| DatabaseField sourceKey = getSourceKeyFields().get(index); |
| Object sourceKeyValue = query.getTranslationRow().get(sourceKey); |
| databaseRow.put(referenceKey, sourceKeyValue); |
| } |
| |
| int orderIndex = 0; |
| // Extract target field and its value. Construct insert statement and execute it |
| for (Object iter = containerPolicy.iteratorFor(objects); containerPolicy.hasNext(iter);) { |
| Object wrappedObject = containerPolicy.nextEntry(iter, query.getSession()); |
| Object object = containerPolicy.unwrapIteratorResult(wrappedObject); |
| if (getValueConverter() != null) { |
| object = getValueConverter().convertObjectValueToDataValue(object, query.getSession()); |
| } |
| databaseRow.put(getDirectField(), object); |
| |
| // In the uow data queries are cached until the end of the commit. |
| if (query.shouldCascadeOnlyDependentParts()) { |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = new Object[3]; |
| event[0] = Insert; |
| event[1] = getInsertQuery(); |
| event[2] = databaseRow.clone(); |
| if(listOrderField != null) { |
| ((AbstractRecord)event[2]).put(listOrderField, orderIndex++); |
| } |
| query.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } else { |
| query.getSession().executeQuery(getInsertQuery(), databaseRow); |
| if ((getHistoryPolicy() != null) && getHistoryPolicy().shouldHandleWrites()) { |
| getHistoryPolicy().mappingLogicalInsert(getInsertQuery(), databaseRow, query.getSession()); |
| } |
| } |
| containerPolicy.propogatePostInsert(query, wrappedObject); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Convert the attribute value to a field value. |
| * Process any converter if defined. |
| */ |
| public Object getFieldValue(Object attributeValue, AbstractSession session) { |
| if (this.valueConverter != null) { |
| return this.valueConverter.convertObjectValueToDataValue(attributeValue, session); |
| } |
| return attributeValue; |
| } |
| |
| /** |
| * INTERNAL: |
| * Convert the field value to an attribute value. |
| * Process any converter if defined. |
| */ |
| public Object getObjectValue(Object fieldValue, AbstractSession session) { |
| if (this.valueConverter != null) { |
| return this.valueConverter.convertDataValueToObjectValue(fieldValue, session); |
| } |
| return fieldValue; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return source key fields for translation by an AggregateObjectMapping |
| */ |
| @Override |
| public Vector getFieldsForTranslationInAggregate() { |
| return getSourceKeyFields(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Update private owned part. |
| */ |
| @Override |
| public void postUpdate(WriteObjectQuery writeQuery) throws DatabaseException { |
| if (isReadOnly()) { |
| return; |
| } |
| |
| if (writeQuery.getObjectChangeSet() != null) { |
| if(this.listOrderField != null) { |
| postUpdateWithChangeSetListOrder(writeQuery); |
| } else { |
| postUpdateWithChangeSet(writeQuery); |
| } |
| return; |
| } |
| |
| // If objects are not instantiated that means they are not changed. |
| if (!isAttributeValueInstantiatedOrChanged(writeQuery.getObject())) { |
| return; |
| } |
| |
| if (writeQuery.getSession().isUnitOfWork()) { |
| if (compareObjects(writeQuery.getObject(), writeQuery.getBackupClone(), writeQuery.getSession())) { |
| return;// Nothing has changed, no work required |
| } |
| } |
| |
| DeleteObjectQuery deleteQuery = new DeleteObjectQuery(); |
| deleteQuery.setObject(writeQuery.getObject()); |
| deleteQuery.setSession(writeQuery.getSession()); |
| deleteQuery.setTranslationRow(writeQuery.getTranslationRow()); |
| |
| if (writeQuery.shouldCascadeOnlyDependentParts()) { |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = new Object[3]; |
| event[0] = DeleteAll; |
| event[1] = deleteQuery; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } else { |
| preDelete(deleteQuery); |
| } |
| postInsert(writeQuery); |
| } |
| |
| /** |
| * INTERNAL: |
| * Update private owned part. |
| */ |
| protected void postUpdateWithChangeSet(WriteObjectQuery writeQuery) throws DatabaseException { |
| ObjectChangeSet changeSet = writeQuery.getObjectChangeSet(); |
| DirectCollectionChangeRecord changeRecord = (DirectCollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName()); |
| if (changeRecord == null) { |
| return; |
| } |
| for (int index = 0; index < getReferenceKeyFields().size(); index++) { |
| DatabaseField referenceKey = getReferenceKeyFields().get(index); |
| DatabaseField sourceKey = getSourceKeyFields().get(index); |
| Object sourceKeyValue = writeQuery.getTranslationRow().get(sourceKey); |
| writeQuery.getTranslationRow().put(referenceKey, sourceKeyValue); |
| } |
| for (Iterator iterator = changeRecord.getRemoveObjectMap().keySet().iterator(); |
| iterator.hasNext();) { |
| Object object = iterator.next(); |
| AbstractRecord thisRow = writeQuery.getTranslationRow().clone(); |
| Object value = getFieldValue(object, writeQuery.getSession()); |
| |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = new Object[3]; |
| event[0] = Delete; |
| if (value == null) { // Bug 306075 - for deleting a null value from a collection |
| event[1] = getDeleteNullQuery(); |
| } else { |
| thisRow.add(getDirectField(), value); |
| event[1] = getDeleteQuery(); |
| } |
| event[2] = thisRow; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event); |
| Integer count = (Integer)changeRecord.getCommitAddMap().get(object); |
| if (count != null) { |
| for (int counter = count; counter > 0; --counter) { |
| thisRow = writeQuery.getTranslationRow().clone(); |
| thisRow.add(getDirectField(), value); |
| // Hey I might actually want to use an inner class here... ok array for now. |
| event = new Object[3]; |
| event[0] = Insert; |
| event[1] = getInsertQuery(); |
| event[2] = thisRow; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } |
| } |
| } |
| for (Iterator iterator = changeRecord.getAddObjectMap().keySet().iterator(); |
| iterator.hasNext();) { |
| Object object = iterator.next(); |
| Integer count = (Integer)changeRecord.getAddObjectMap().get(object); |
| for (int counter = count; counter > 0; --counter) { |
| AbstractRecord thisRow = writeQuery.getTranslationRow().clone(); |
| Object value = object; |
| if (getValueConverter() != null) { |
| value = getValueConverter().convertObjectValueToDataValue(value, writeQuery.getSession()); |
| } |
| thisRow.add(getDirectField(), value); |
| |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = new Object[3]; |
| event[0] = Insert; |
| event[1] = getInsertQuery(); |
| event[2] = thisRow; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Update private owned part. |
| */ |
| protected void postUpdateWithChangeSetListOrder(WriteObjectQuery writeQuery) throws DatabaseException { |
| ObjectChangeSet changeSet = writeQuery.getObjectChangeSet(); |
| DirectCollectionChangeRecord changeRecord = (DirectCollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName()); |
| if (changeRecord == null) { |
| return; |
| } |
| for (int index = 0; index < getReferenceKeyFields().size(); index++) { |
| DatabaseField referenceKey = getReferenceKeyFields().get(index); |
| DatabaseField sourceKey = getSourceKeyFields().get(index); |
| Object sourceKeyValue = writeQuery.getTranslationRow().get(sourceKey); |
| writeQuery.getTranslationRow().put(referenceKey, sourceKeyValue); |
| } |
| |
| boolean shouldRepairOrder = false; |
| if(changeRecord.getLatestCollection() instanceof IndirectList) { |
| shouldRepairOrder = ((IndirectList)changeRecord.getLatestCollection()).isListOrderBrokenInDb(); |
| } |
| if(shouldRepairOrder) { |
| // delete all members of collection |
| DeleteObjectQuery deleteQuery = new DeleteObjectQuery(); |
| deleteQuery.setObject(writeQuery.getObject()); |
| deleteQuery.setSession(writeQuery.getSession()); |
| deleteQuery.setTranslationRow(writeQuery.getTranslationRow()); |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] eventDeleteAll = new Object[2]; |
| eventDeleteAll[0] = DeleteAll; |
| eventDeleteAll[1] = deleteQuery; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, eventDeleteAll); |
| |
| // re-insert them back |
| for(int i=0; i < ((List)changeRecord.getLatestCollection()).size(); i++) { |
| Object value = ((List)changeRecord.getLatestCollection()).get(i); |
| value = getFieldValue(value, writeQuery.getSession()); |
| AbstractRecord insertRow = writeQuery.getTranslationRow().clone(); |
| insertRow.add(getDirectField(), value); |
| insertRow.add(this.listOrderField, i); |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = new Object[3]; |
| event[0] = Insert; |
| event[1] = getInsertQuery(); |
| event[2] = insertRow; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } |
| |
| ((IndirectList)changeRecord.getLatestCollection()).setIsListOrderBrokenInDb(false); |
| changeRecord.setOrderHasBeenRepaired(true); |
| return; |
| } |
| |
| if(changeRecord.getChangedIndexes() == null) { |
| compareListsForChange((List)changeRecord.getOriginalCollection(), (List)changeRecord.getLatestCollection(), changeRecord, writeQuery.getSession()); |
| } |
| |
| Iterator<Map.Entry<Object, Set[]>> it = changeRecord.getChangedIndexes().entrySet().iterator(); |
| while(it.hasNext()) { |
| Map.Entry<Object, Set[]> entry = it.next(); |
| Object value = entry.getKey(); |
| if (getValueConverter() != null) { |
| value = getValueConverter().convertObjectValueToDataValue(value, writeQuery.getSession()); |
| } |
| |
| Set[] indexes = entry.getValue(); |
| Set indexesBefore = indexes[0]; |
| Set indexesAfter = indexes[1]; |
| |
| if(indexesAfter == null) { |
| // All copies of the target object deleted - don't need to verify order field contents. |
| AbstractRecord deleteRow = writeQuery.getTranslationRow().clone(); |
| |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = new Object[3]; |
| event[0] = Delete; |
| if (value == null) { // Bug 306075 - for deleting a null value from a collection |
| event[1] = getDeleteNullQuery(); |
| } else { |
| deleteRow.add(getDirectField(), value); |
| event[1] = getDeleteQuery(); |
| } |
| event[2] = deleteRow; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } else if(indexesAfter.isEmpty()) { |
| // Some copies of the target objects should be deleted, some left in the db |
| Iterator<Integer> itBefore = indexesBefore.iterator(); |
| while(itBefore.hasNext()) { |
| AbstractRecord deleteAtIndexRow = writeQuery.getTranslationRow().clone(); |
| deleteAtIndexRow.add(getDirectField(), value); |
| deleteAtIndexRow.add(this.listOrderField, itBefore.next()); |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = new Object[3]; |
| event[0] = DeleteAtIndex; |
| event[1] = deleteAtIndexQuery; |
| event[2] = deleteAtIndexRow; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } |
| } else { |
| if(indexesBefore == null || indexesBefore.isEmpty()) { |
| // insert the object for each index in indexesAfter |
| Iterator<Integer> itAfter = indexesAfter.iterator(); |
| while(itAfter.hasNext()) { |
| AbstractRecord insertRow = writeQuery.getTranslationRow().clone(); |
| insertRow.add(getDirectField(), value); |
| insertRow.add(this.listOrderField, itAfter.next()); |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = new Object[3]; |
| event[0] = Insert; |
| event[1] = getInsertQuery(); |
| event[2] = insertRow; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } |
| } else { |
| Iterator<Integer> itBefore = indexesBefore.iterator(); |
| Iterator<Integer> itAfter = indexesAfter.iterator(); |
| while(itBefore.hasNext() || itAfter.hasNext()) { |
| if(itBefore.hasNext()) { |
| if(itAfter.hasNext()) { |
| // update the object changing index from indexBefore to indexAfter |
| AbstractRecord updateAtIndexRow = writeQuery.getTranslationRow().clone(); |
| updateAtIndexRow.add(getDirectField(), value); |
| updateAtIndexRow.add(this.listOrderField, itBefore.next()); |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = new Object[4]; |
| event[0] = UpdateAtIndex; |
| event[1] = updateAtIndexQuery; |
| event[2] = updateAtIndexRow; |
| DatabaseRecord modifyRow = new DatabaseRecord(1); |
| modifyRow.add(this.listOrderField, itAfter.next()); |
| event[3] = modifyRow; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } else { |
| // delete the object at indexBefore |
| AbstractRecord deleteAtIndexRow = writeQuery.getTranslationRow().clone(); |
| deleteAtIndexRow.add(getDirectField(), value); |
| deleteAtIndexRow.add(this.listOrderField, itBefore.next()); |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = new Object[3]; |
| event[0] = DeleteAtIndex; |
| event[1] = deleteAtIndexQuery; |
| event[2] = deleteAtIndexRow; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } |
| } else { |
| // itAfter.hasNext() must be true |
| // insert the object at indexAfter |
| AbstractRecord insertRow = writeQuery.getTranslationRow().clone(); |
| insertRow.add(getDirectField(), value); |
| insertRow.add(this.listOrderField, itAfter.next()); |
| // Hey I might actually want to use an inner class here... ok array for now. |
| Object[] event = new Object[3]; |
| event[0] = Insert; |
| event[1] = getInsertQuery(); |
| event[2] = insertRow; |
| writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Delete private owned part. Which is a collection of objects from the reference table. |
| */ |
| @Override |
| public void preDelete(DeleteObjectQuery query) throws DatabaseException { |
| if (this.isReadOnly) { |
| return; |
| } |
| |
| if (!this.isCascadeOnDeleteSetOnDatabase) { |
| prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getDescriptor(), query.getSession()); |
| query.getSession().executeQuery(this.deleteAllQuery, query.getTranslationRow()); |
| } |
| if ((this.historyPolicy != null) && this.historyPolicy.shouldHandleWrites()) { |
| if (this.isCascadeOnDeleteSetOnDatabase) { |
| prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getDescriptor(), query.getSession()); |
| } |
| this.historyPolicy.mappingLogicalDelete(this.deleteAllQuery, query.getTranslationRow(), query.getSession()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * The translation row may require additional fields than the primary key if the mapping in not on the primary key. |
| */ |
| @Override |
| protected void prepareTranslationRow(AbstractRecord translationRow, Object object, ClassDescriptor descriptor, AbstractSession session) { |
| // Make sure that each source key field is in the translation row. |
| for (Enumeration<DatabaseField> sourceFieldsEnum = getSourceKeyFields().elements(); |
| sourceFieldsEnum.hasMoreElements();) { |
| DatabaseField sourceKey = sourceFieldsEnum.nextElement(); |
| if (!translationRow.containsKey(sourceKey)) { |
| Object value = descriptor.getObjectBuilder().extractValueFromObjectForField(object, sourceKey, session); |
| translationRow.put(sourceKey, value); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Used by DirectMapMapping to rebuild select query. |
| */ |
| protected void initOrRebuildSelectQuery() { |
| this.selectionQuery.setSQLStatement(new SQLSelectStatement()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Overridden by mappings that require additional processing of the change record after the record has been calculated. |
| */ |
| @Override |
| public void recordPrivateOwnedRemovals(Object object, UnitOfWorkImpl uow) { |
| } |
| |
| /** |
| * INTERNAL: |
| * Once descriptors are serialized to the remote session. All its mappings and reference descriptors are traversed. Usually |
| * mappings are initialized and serialized reference descriptors are replaced with local descriptors if they already exist on the |
| * remote session. |
| */ |
| @Override |
| public void remoteInitialization(DistributedSession session) { |
| // Remote mappings is initialized here again because while serializing only the uninitialized data is passed |
| // as the initialized data is not serializable. |
| if (!isRemotelyInitialized()) { |
| getAttributeAccessor().initializeAttributes(getDescriptor().getJavaClass()); |
| remotelyInitialized(); |
| } |
| } |
| |
| |
| /** |
| * INTERNAL: |
| * replace the value holders in the specified reference object(s) |
| */ |
| @Override |
| public Map replaceValueHoldersIn(Object object, RemoteSessionController controller) { |
| // do nothing, since direct collections do not hold onto other domain objects |
| return null; |
| } |
| |
| /** |
| * PUBLIC: |
| * Some databases do not properly support all of the base data types. For these databases, |
| * the base data type must be explicitly specified in the mapping to tell EclipseLink to force |
| * the instance variable value to that data type. |
| * @since Java Persistence API 2.0 |
| * Migrated from AbstractDirectMapping |
| */ |
| public void setAttributeClassification(Class<?> attributeClassification) { |
| this.attributeClassification = attributeClassification; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the name of the class for MW usage. |
| * @since Java Persistence API 2.0 |
| * Migrated from AbstractDirectMapping |
| */ |
| public void setAttributeClassificationName(String attributeClassificationName) { |
| this.attributeClassificationName = attributeClassificationName; |
| } |
| |
| protected void setDeleteQuery(ModifyQuery query) { |
| this.changeSetDeleteQuery = query; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the receiver's delete SQL string. This allows the user to override the SQL |
| * generated by TopLink, with there own SQL or procedure call. The arguments are |
| * translated from the fields of the source row, through replacing the field names |
| * marked by '#' with the values for those fields. |
| * This SQL is responsible for doing the deletion required by the mapping, |
| * such as deletion from join table for M-M. |
| * Example, 'delete from RESPONS where EMP_ID = #EMP_ID and DESCRIP = #DESCRIP'. |
| */ |
| public void setDeleteSQLString(String sqlString) { |
| DataModifyQuery query = new DataModifyQuery(); |
| query.setSQLString(sqlString); |
| setCustomDeleteQuery(query); |
| } |
| |
| /** |
| * ADVANCED: |
| * Configure the mapping to use a container policy. |
| * The policy manages the access to the collection. |
| */ |
| @Override |
| public void setContainerPolicy(ContainerPolicy containerPolicy) { |
| this.containerPolicy = containerPolicy; |
| if (this.selectionQuery.isDataReadQuery()){ |
| ((DataReadQuery) getSelectionQuery()).setContainerPolicy(containerPolicy); |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * The default delete query for this mapping can be overridden by specifying the new query. |
| * This query is responsible for doing the deletion required by the mapping, |
| * such as deletion from join table for M-M. The query should delete a specific row from the |
| * DirectCollectionTable bases on the DirectField. |
| */ |
| public void setCustomDeleteQuery(ModifyQuery query) { |
| setDeleteQuery(query); |
| setHasCustomDeleteQuery(true); |
| } |
| |
| /** |
| * PUBLIC: |
| * The default delete by index query for this mapping can be overridden by specifying the new query. |
| * This query used (only in case listOrderField != null) to delete object with particular orderFieldValue. |
| */ |
| public void setCustomDeleteAtIndexQuery(ModifyQuery query) { |
| this.deleteAtIndexQuery = query; |
| hasCustomDeleteAtIndexQuery = true; |
| } |
| |
| /** |
| * PUBLIC: |
| * The default insert query for mapping can be overridden by specifying the new query. |
| * This query inserts the row into the direct table. |
| */ |
| public void setCustomInsertQuery(DataModifyQuery query) { |
| setInsertQuery(query); |
| setHasCustomInsertQuery(true); |
| } |
| |
| /** |
| * PUBLIC: |
| * The default delete by index query for this mapping can be overridden by specifying the new query. |
| * This query used (only in case listOrderField != null) to update orderFieldValue of object with particular orderFieldValue. |
| */ |
| public void setCustomUpdateAtIndexQuery(ModifyQuery query) { |
| this.updateAtIndexQuery = query; |
| hasCustomUpdateAtIndexQuery = true; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the direct field in the reference table. |
| * This is the field that the primitive data value is stored in. |
| */ |
| public void setDirectField(DatabaseField field) { |
| directField = field; |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the class type of the field value. |
| * This can be used if field value differs from the object value, |
| * has specific typing requirements such as usage of java.sql.Blob or NChar. |
| * This must be called after the field name has been set. |
| */ |
| public void setDirectFieldClassification(Class<?> fieldType) { |
| getDirectField().setType(fieldType); |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the class type of the field value. |
| * This can be used if field value differs from the object value, |
| * has specific typing requirements such as usage of java.sql.Blob or NChar. |
| * This must be called after the field name has been set. |
| */ |
| public void setDirectFieldClassificationName(String className) { |
| getDirectField().setTypeName(className); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the direct field name in the reference table. |
| * This is the field that the primitive data value is stored in. |
| */ |
| public void setDirectFieldName(String fieldName) { |
| setDirectField(new DatabaseField(fieldName)); |
| } |
| |
| protected void setHasCustomDeleteQuery(boolean bool) { |
| hasCustomDeleteQuery = bool; |
| } |
| |
| protected void setHasCustomInsertQuery(boolean bool) { |
| hasCustomInsertQuery = bool; |
| } |
| |
| protected void setInsertQuery(DataModifyQuery insertQuery) { |
| this.insertQuery = insertQuery; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the receiver's insert SQL string. This allows the user to override the SQL |
| * generated by TopLink, with there own SQL or procedure call. The arguments are |
| * translated from the fields of the source row, through replacing the field names |
| * marked by '#' with the values for those fields. |
| * This is used to insert an entry into the direct table. |
| * <p>Example, 'insert into RESPONS (EMP_ID, RES_DESC) values (#EMP_ID, #RES_DESC)'. |
| */ |
| public void setInsertSQLString(String sqlString) { |
| DataModifyQuery query = new DataModifyQuery(); |
| query.setSQLString(sqlString); |
| setCustomInsertQuery(query); |
| } |
| |
| /** |
| * INTERNAL: |
| * This cannot be used with direct collection mappings. |
| */ |
| @Override |
| public void setReferenceClass(Class<?> referenceClass) { |
| return; |
| } |
| |
| @Override |
| public void setReferenceClassName(String referenceClassName) { |
| return; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the name of the reference key field. |
| * This is the foreign key field in the direct table referencing the primary key of the source object. |
| * This method is used if the reference key consists of only a single field. |
| */ |
| public void setReferenceKeyFieldName(String fieldName) { |
| getReferenceKeyFields().addElement(new DatabaseField(fieldName)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the reference key field names associated with the mapping. |
| * These must be in-order with the sourceKeyFieldNames. |
| */ |
| public void setReferenceKeyFieldNames(Vector fieldNames) { |
| Vector fields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size()); |
| for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) { |
| fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement())); |
| } |
| |
| setReferenceKeyFields(fields); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the reference fields. |
| */ |
| public void setReferenceKeyFields(Vector<DatabaseField> aVector) { |
| this.referenceKeyFields = aVector; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the reference table. |
| */ |
| public void setReferenceTable(DatabaseTable table) { |
| referenceTable = table; |
| } |
| |
| /** |
| * PUBLIC: |
| * Sets the selection criteria to be used as a where clause to read |
| * reference objects. This criteria is automatically generated by the |
| * TopLink if not explicitly specified by the user. |
| */ |
| @Override |
| public void setSelectionCriteria(Expression anExpression) { |
| if (getSelectionQuery().isReadAllQuery()){ |
| getSelectionQuery().setSelectionCriteria(anExpression); |
| } else { |
| getSelectionQuery().getSQLStatement().setWhereClause(anExpression); |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the reference table name. |
| */ |
| public void setReferenceTableName(String tableName) { |
| if (tableName == null) { |
| setReferenceTable(null); |
| } else { |
| setReferenceTable(new DatabaseTable(tableName)); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the container policy on the selection query for this mapping. |
| */ |
| @Override |
| protected void setSelectionQueryContainerPolicy(ContainerPolicy containerPolicy) { |
| ((DataReadQuery) getSelectionQuery()).setContainerPolicy(containerPolicy); |
| } |
| |
| /** |
| * PUBLIC: |
| * Support history on the reference table. |
| */ |
| public void setHistoryPolicy(HistoryPolicy policy) { |
| this.historyPolicy = policy; |
| if (policy != null) { |
| policy.setMapping(this); |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the name of the session to execute the mapping's queries under. |
| * This can be used by the session broker to override the default session |
| * to be used for the target class. |
| */ |
| @Override |
| public void setSessionName(String name) { |
| super.setSessionName(name); |
| getInsertQuery().setSessionName(name); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the source key field names associated with the mapping. |
| * These must be in-order with the referenceKeyFieldNames. |
| */ |
| public void setSourceKeyFieldNames(Vector fieldNames) { |
| Vector fields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size()); |
| for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) { |
| fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement())); |
| } |
| |
| setSourceKeyFields(fields); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the source fields. |
| */ |
| public void setSourceKeyFields(Vector<DatabaseField> sourceKeyFields) { |
| this.sourceKeyFields = sourceKeyFields; |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to store the FK fields that can be cached that correspond to noncacheable mappings |
| * the FK field values will be used to re-issue the query when cloning the shared cache entity |
| */ |
| @Override |
| public void collectQueryParameters(Set<DatabaseField> cacheFields){ |
| for (DatabaseField field : getSourceKeyFields()) { |
| cacheFields.add(field); |
| } |
| |
| } |
| |
| /** |
| * INTERNAL: |
| * Used by AttributeLevelChangeTracking to update a changeRecord with calculated changes |
| * as apposed to detected changes. If an attribute can not be change tracked it's |
| * changes can be detected through this process. |
| */ |
| @Override |
| public void calculateDeferredChanges(ChangeRecord changeRecord, AbstractSession session) { |
| DirectCollectionChangeRecord collectionRecord = (DirectCollectionChangeRecord)changeRecord; |
| // TODO: Handle events that fired after collection was replaced. |
| compareCollectionsForChange(collectionRecord.getOriginalCollection(), collectionRecord.getLatestCollection(), collectionRecord, session); |
| } |
| |
| /** |
| * ADVANCED: |
| * This method is used to have an object add to a collection once the changeSet is applied |
| * The referenceKey parameter should only be used for direct Maps. |
| */ |
| @Override |
| public void simpleAddToCollectionChangeRecord(Object referenceKey, Object objectToAdd, ObjectChangeSet changeSet, AbstractSession session) { |
| simpleAddToCollectionChangeRecord(objectToAdd, null, false, changeSet, session, true); |
| } |
| |
| protected void simpleAddToCollectionChangeRecord(Object objectToAdd, Integer index, boolean isSet, ObjectChangeSet changeSet, AbstractSession session, boolean isChangeApplied) { |
| DirectCollectionChangeRecord collectionChangeRecord = (DirectCollectionChangeRecord)changeSet.getChangesForAttributeNamed(getAttributeName()); |
| if (collectionChangeRecord == null) { |
| collectionChangeRecord = new DirectCollectionChangeRecord(changeSet); |
| collectionChangeRecord.setAttribute(getAttributeName()); |
| collectionChangeRecord.setMapping(this); |
| changeSet.addChange(collectionChangeRecord); |
| Object collection = getRealAttributeValueFromObject(changeSet.getUnitOfWorkClone(), session); |
| if(this.listOrderField != null) { |
| List originalListCopy = new ArrayList((List)collection); |
| // collection already contains the added object - to bring it to the original state it should be removed |
| if(index == null) { |
| originalListCopy.remove(originalListCopy.size() - 1); |
| } else { |
| // intValue() is essential - otherwise invokes remove(Object) |
| originalListCopy.remove(index.intValue()); |
| } |
| collectionChangeRecord.setOriginalCollection(originalListCopy); |
| collectionChangeRecord.setLatestCollection(collection); |
| } else { |
| collectionChangeRecord.storeDatabaseCounts(collection, getContainerPolicy(), session); |
| collectionChangeRecord.setFirstToAddAlreadyInCollection(isChangeApplied); |
| } |
| } |
| if(!collectionChangeRecord.isDeferred() && this.listOrderField == null) { |
| collectionChangeRecord.addAdditionChange(objectToAdd, 1); |
| } |
| } |
| |
| /** |
| * ADVANCED: |
| * This method is used to have an object removed from a collection once the changeSet is applied |
| * The referenceKey parameter should only be used for direct Maps. |
| */ |
| @Override |
| public void simpleRemoveFromCollectionChangeRecord(Object referenceKey, Object objectToRemove, ObjectChangeSet changeSet, AbstractSession session) { |
| simpleRemoveFromCollectionChangeRecord(objectToRemove, null, false, changeSet, session, true); |
| } |
| |
| protected void simpleRemoveFromCollectionChangeRecord(Object objectToRemove, Integer index, boolean isSet, ObjectChangeSet changeSet, AbstractSession session, boolean isChangeApplied) { |
| DirectCollectionChangeRecord collectionChangeRecord = (DirectCollectionChangeRecord)changeSet.getChangesForAttributeNamed(getAttributeName()); |
| if (collectionChangeRecord == null) { |
| collectionChangeRecord = new DirectCollectionChangeRecord(changeSet); |
| collectionChangeRecord.setAttribute(getAttributeName()); |
| collectionChangeRecord.setMapping(this); |
| changeSet.addChange(collectionChangeRecord); |
| Object collection = getRealAttributeValueFromObject(changeSet.getUnitOfWorkClone(), session); |
| if(this.listOrderField != null) { |
| List originalListCopy = new ArrayList((List)collection); |
| // collection already doesn't contain the removed object - to bring it to the original state it should be added or set back. |
| // index is not null because IndirectList does remove through indexOf. |
| if(isSet) { |
| originalListCopy.set(index, objectToRemove); |
| } else { |
| originalListCopy.add(index, objectToRemove); |
| } |
| collectionChangeRecord.setOriginalCollection(originalListCopy); |
| collectionChangeRecord.setLatestCollection(collection); |
| } else { |
| collectionChangeRecord.storeDatabaseCounts(collection, getContainerPolicy(), session); |
| collectionChangeRecord.setFirstToRemoveAlreadyOutCollection(isChangeApplied); |
| if(isSet) { |
| collectionChangeRecord.setFirstToAddAlreadyInCollection(isChangeApplied); |
| } |
| } |
| } |
| if(!collectionChangeRecord.isDeferred() && this.listOrderField == null) { |
| collectionChangeRecord.addRemoveChange(objectToRemove, 1); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Either create a new change record or update with the new value. This is used |
| * by attribute change tracking. |
| * Specifically in a collection mapping this will be called when the customer |
| * Set a new collection. In this case we will need to mark the change record |
| * with the new and the old versions of the collection. |
| * And mark the ObjectChangeSet with the attribute name then when the changes are calculated |
| * force a compare on the collections to determine changes. |
| */ |
| @Override |
| public void updateChangeRecord(Object clone, Object newValue, Object oldValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) { |
| DirectCollectionChangeRecord collectionChangeRecord = (DirectCollectionChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName()); |
| if (collectionChangeRecord == null) { |
| collectionChangeRecord = new DirectCollectionChangeRecord(objectChangeSet); |
| collectionChangeRecord.setAttribute(getAttributeName()); |
| collectionChangeRecord.setMapping(this); |
| objectChangeSet.addChange(collectionChangeRecord); |
| } |
| collectionChangeRecord.setIsDeferred(true); |
| objectChangeSet.deferredDetectionRequiredOn(getAttributeName()); |
| if (collectionChangeRecord.getOriginalCollection() == null) { |
| collectionChangeRecord.recreateOriginalCollection(oldValue, uow); |
| } |
| collectionChangeRecord.setLatestCollection(newValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * Add or removes a new value and its change set to the collection change record based on the event passed in. This is used by |
| * attribute change tracking. |
| */ |
| @Override |
| public void updateCollectionChangeRecord(CollectionChangeEvent event, ObjectChangeSet changeSet, UnitOfWorkImpl uow) { |
| if (event != null ) { |
| //Letting the mapping create and add the ChangeSet to the ChangeRecord rather |
| // than the policy, since the policy doesn't know how to handle DirectCollectionChangeRecord. |
| // if ordering is to be supported in the future, check how the method in CollectionMapping is implemented |
| Object value = event.getNewValue(); |
| |
| if (event.getChangeType() == CollectionChangeEvent.ADD) { |
| simpleAddToCollectionChangeRecord(value, event.getIndex(), event.isSet(), changeSet, uow, event.isChangeApplied()); |
| } else if (event.getChangeType() == CollectionChangeEvent.REMOVE) { |
| simpleRemoveFromCollectionChangeRecord(value, event.getIndex(), event.isSet(), changeSet, uow, event.isChangeApplied()); |
| } else { |
| throw ValidationException.wrongCollectionChangeEventType(event.getChangeType()); |
| } |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * It is illegal to use a Map as the container of a DirectCollectionMapping. Only |
| * Collection containers are supported for DirectCollectionMappings. |
| * @see org.eclipse.persistence.mappings.DirectMapMapping |
| */ |
| @Override |
| public void useMapClass(Class<?> concreteClass, String methodName) { |
| throw ValidationException.illegalUseOfMapInDirectCollection(this, concreteClass, methodName); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the value of the reference attribute or a value holder. |
| * Check whether the mapping's attribute should be optimized through batch and joining. |
| * Overridden to support flashback/historical queries. |
| */ |
| @Override |
| public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, CacheKey cacheKey, AbstractSession session, boolean isTargetProtected, Boolean[] wasCacheUsed) throws DatabaseException { |
| if (this.descriptor.getCachePolicy().isProtectedIsolation()) { |
| if (this.isCacheable && isTargetProtected && cacheKey != null) { |
| //cachekey will be null when isolating to uow |
| //used cached collection |
| Object result = null; |
| Object cached = cacheKey.getObject(); |
| if (cached != null) { |
| if (wasCacheUsed != null){ |
| wasCacheUsed[0] = Boolean.TRUE; |
| } |
| return this.getAttributeValueFromObject(cached); |
| } |
| return result; |
| } else if (!this.isCacheable && !isTargetProtected && cacheKey != null) { |
| return this.indirectionPolicy.buildIndirectObject(new ValueHolder<>(null)); |
| } |
| } |
| if (row.hasSopObject()) { |
| return getAttributeValueFromObject(row.getSopObject()); |
| } |
| if (sourceQuery.isObjectLevelReadQuery() && (((ObjectLevelReadQuery)sourceQuery).isAttributeBatchRead(this.descriptor, getAttributeName()) |
| || (sourceQuery.isReadAllQuery() && shouldUseBatchReading()))) { |
| return batchedValueFromRow(row, (ObjectLevelReadQuery)sourceQuery, cacheKey); |
| } |
| |
| if (shouldUseValueFromRowWithJoin(joinManager, sourceQuery)) { |
| return valueFromRowInternalWithJoin(row, joinManager, sourceQuery, cacheKey, session, isTargetProtected); |
| } |
| |
| // if the query uses batch reading, return a special value holder |
| // or retrieve the object from the query property. |
| ReadQuery targetQuery = getSelectionQuery(); |
| |
| boolean extendingPessimisticLockScope = isExtendingPessimisticLockScope(sourceQuery) && extendPessimisticLockScope == ExtendPessimisticLockScope.TARGET_QUERY; |
| if ((getHistoryPolicy() != null) || (sourceQuery.getSession().getAsOfClause() != null) || ((sourceQuery.isObjectLevelReadQuery() && ((ObjectLevelReadQuery)sourceQuery).hasAsOfClause()) && (sourceQuery.shouldCascadeAllParts() || (sourceQuery.shouldCascadePrivateParts() && isPrivateOwned()) || (sourceQuery.shouldCascadeByMapping() && this.cascadeRefresh))) || extendingPessimisticLockScope) { |
| targetQuery = (ReadQuery)targetQuery.clone(); |
| // Code copied roughly from initializeSelectionStatement. |
| SQLSelectStatement statement = new SQLSelectStatement(); |
| statement.addTable(getReferenceTable()); |
| statement.addField(getDirectField().clone()); |
| if (isDirectMapMapping()) { |
| statement.addField(((DirectMapMapping)this).getDirectKeyField().clone()); |
| } |
| statement.setWhereClause((Expression)getSelectionCriteria().clone()); |
| if (sourceQuery.isObjectLevelReadQuery()) { |
| statement.getBuilder().asOf(((ObjectLevelReadQuery)sourceQuery).getAsOfClause()); |
| } |
| if (extendingPessimisticLockScope) { |
| statement.setLockingClause(new ForUpdateClause(sourceQuery.getLockMode())); |
| } |
| if (getHistoryPolicy() != null) { |
| ExpressionBuilder builder = statement.getBuilder(); |
| if (sourceQuery.getSession().getAsOfClause() != null) { |
| builder.asOf(sourceQuery.getSession().getAsOfClause()); |
| } else if (builder.getAsOfClause() == null) { |
| builder.asOf(AsOfClause.NO_CLAUSE); |
| } |
| Expression temporalExpression = getHistoryPolicy().additionalHistoryExpression(builder, builder); |
| statement.setWhereClause(statement.getWhereClause().and(temporalExpression)); |
| if (builder.hasAsOfClause()) { |
| statement.getTables().set(0, getHistoryPolicy().getHistoricalTables().get(0)); |
| } |
| } |
| statement.normalize(sourceQuery.getSession(), null); |
| targetQuery.setSQLStatement(statement); |
| } |
| |
| return getIndirectionPolicy().valueFromQuery(targetQuery, row, sourceQuery.getSession()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Checks if object is deleted from the database or not. |
| */ |
| @Override |
| public boolean verifyDelete(Object object, AbstractSession session) throws DatabaseException { |
| // Row is built for translation |
| if (isReadOnly()) { |
| return true; |
| } |
| |
| AbstractRecord row = getDescriptor().getObjectBuilder().buildRowForTranslation(object, session); |
| Object value = session.executeQuery(getSelectionQuery(), row); |
| |
| return getContainerPolicy().isEmpty(value); |
| } |
| |
| /** |
| * INTERNAL: |
| * DirectCollectionMapping contents should not be considered for addition to the UnitOfWork |
| * private owned objects list for removal. |
| */ |
| @Override |
| public boolean isCandidateForPrivateOwnedRemoval() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL |
| * Return true if this mapping supports cascaded version optimistic locking. |
| */ |
| @Override |
| public boolean isCascadedLockingSupported() { |
| return true; |
| } |
| } |