| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2020 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 |
| // 05/14/2012-2.4 Guy Pelletier |
| // - 376603: Provide for table per tenant support for multitenant applications |
| // 02/11/2013-2.5 Guy Pelletier |
| // - 365931: @JoinColumn(name="FK_DEPT",insertable = false, updatable = true) causes INSERT statement to include this data value that it is associated with |
| package org.eclipse.persistence.mappings; |
| |
| import java.util.*; |
| |
| import org.eclipse.persistence.annotations.CacheKeyType; |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.exceptions.*; |
| import org.eclipse.persistence.expressions.*; |
| import org.eclipse.persistence.internal.helper.*; |
| import org.eclipse.persistence.internal.identitymaps.*; |
| import org.eclipse.persistence.internal.indirection.ProxyIndirectionPolicy; |
| import org.eclipse.persistence.internal.queries.ContainerPolicy; |
| import org.eclipse.persistence.internal.queries.JoinedAttributeManager; |
| import org.eclipse.persistence.internal.queries.MappedKeyMapContainerPolicy; |
| import org.eclipse.persistence.internal.sessions.*; |
| import org.eclipse.persistence.sessions.DatabaseRecord; |
| import org.eclipse.persistence.queries.*; |
| import org.eclipse.persistence.internal.descriptors.CascadeLockingPolicy; |
| import org.eclipse.persistence.internal.descriptors.DescriptorIterator; |
| import org.eclipse.persistence.internal.descriptors.ObjectBuilder; |
| import org.eclipse.persistence.internal.expressions.ConstantExpression; |
| import org.eclipse.persistence.internal.expressions.ObjectExpression; |
| import org.eclipse.persistence.internal.expressions.FieldExpression; |
| import org.eclipse.persistence.internal.expressions.ParameterExpression; |
| import org.eclipse.persistence.internal.expressions.QueryKeyExpression; |
| import org.eclipse.persistence.internal.expressions.SQLSelectStatement; |
| import org.eclipse.persistence.mappings.foundation.MapKeyMapping; |
| import org.eclipse.persistence.mappings.querykeys.OneToOneQueryKey; |
| import org.eclipse.persistence.mappings.querykeys.QueryKey; |
| |
| /** |
| * <p><b>Purpose</b>: One to one mappings are used to represent a pointer references |
| * between two java objects. This mappings is usually represented by a single pointer |
| * (stored in an instance variable) between the source and target objects. In the relational |
| * database tables, these mappings are normally implemented using foreign keys. |
| * |
| * @author Sati |
| * @since TOPLink/Java 1.0 |
| */ |
| public class OneToOneMapping extends ObjectReferenceMapping implements RelationalMapping, MapKeyMapping { |
| |
| /** Maps the source foreign/primary key fields to the target primary/foreign key fields. */ |
| protected Map<DatabaseField, DatabaseField> sourceToTargetKeyFields; |
| |
| /** Maps the target primary/foreign key fields to the source foreign/primary key fields. */ |
| protected Map<DatabaseField, DatabaseField> targetToSourceKeyFields; |
| |
| /** Keeps track of which fields are foreign keys on a per field basis (can have mixed foreign key relationships). */ |
| /** These are used for non-unit of work modification to check if the value of the 1-1 was changed and a deletion is required. */ |
| protected boolean shouldVerifyDelete; |
| protected transient Expression privateOwnedCriteria; |
| |
| public DatabaseTable keyTableForMapKey = null; |
| |
| protected static final String setObject = "setObject"; |
| |
| /** Mechanism holds relationTable and all fields and queries associated with it. */ |
| protected RelationTableMechanism mechanism; |
| |
| /** |
| * Define if this mapping is really for a OneToOne relationship. |
| * This is a backward compatibility issue, in that before the ManyToOneMapping |
| * was created OneToOneMapping was used for both. |
| */ |
| protected boolean isOneToOneRelationship = false; |
| |
| /** |
| * Defines if this mapping was built using primary key join columns. |
| */ |
| protected boolean isOneToOnePrimaryKeyRelationship = false; |
| |
| /** |
| * Keep track of which fields are insertable and updatable. |
| */ |
| protected HashSet<DatabaseField> insertableFields = new HashSet<>(); |
| protected HashSet<DatabaseField> updatableFields = new HashSet<>(); |
| |
| /** |
| * Keep a reference to the source and target expressions to post initialize |
| * when building a selection criteria early. |
| */ |
| protected transient List<Expression> sourceExpressionsToPostInitialize; |
| protected transient List<Expression> targetExpressionsToPostInitialize; |
| |
| /** |
| * Mode for writeFromObjectIntoRowInternal method |
| */ |
| protected enum ShallowMode { |
| Insert, |
| UpdateAfterInsert, |
| UpdateBeforeDelete |
| } |
| |
| /** |
| * PUBLIC: |
| * Default constructor. |
| */ |
| public OneToOneMapping() { |
| this.selectionQuery = new ReadObjectQuery(); |
| this.sourceToTargetKeyFields = new HashMap(2); |
| this.targetToSourceKeyFields = new HashMap(2); |
| this.foreignKeyFields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1); |
| this.sourceExpressionsToPostInitialize = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1); |
| this.targetExpressionsToPostInitialize = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1); |
| this.isForeignKeyRelationship = false; |
| this.shouldVerifyDelete = true; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isRelationalMapping() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used when initializing queries for mappings that use a Map. |
| * Called when the selection query is being initialized to add the fields for the map key to the query. |
| */ |
| @Override |
| public void addAdditionalFieldsToQuery(ReadQuery selectionQuery, Expression baseExpression){ |
| for (DatabaseField field : getForeignKeyFields()) { |
| if (selectionQuery.isObjectLevelReadQuery()){ |
| ((ObjectLevelReadQuery)selectionQuery).addAdditionalField(baseExpression.getField(field)); |
| } else if (selectionQuery.isDataReadQuery()){ |
| ((SQLSelectStatement) selectionQuery.getSQLStatement()).addField(field); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Used when initializing queries for mappings that use a Map |
| * Called when the insert query is being initialized to ensure the fields for the map key are in the insert query |
| */ |
| @Override |
| public void addFieldsForMapKey(AbstractRecord joinRow){ |
| Iterator<DatabaseField> i = getForeignKeyFields().iterator(); |
| while (i.hasNext()){ |
| joinRow.put(i.next(), null); |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Define the foreign key relationship in the 1-1 mapping. |
| * This method is used for composite foreign key relationships, |
| * that is the source object's table has multiple foreign key fields to |
| * the target object's primary key fields. |
| * Both the source foreign key field and the target foreign key field must |
| * be specified. |
| * When a foreign key is specified TopLink will automatically populate the |
| * value for that field from the target object when the object is written to |
| * the database. If the foreign key is also mapped through a direct-to-field |
| * then the direct-to-field must be set read-only. |
| */ |
| @Override |
| public void addForeignKeyField(DatabaseField sourceForeignKeyField, DatabaseField targetPrimaryKeyField) { |
| setIsForeignKeyRelationship(true); |
| getForeignKeyFields().addElement(sourceForeignKeyField); |
| |
| getSourceToTargetKeyFields().put(sourceForeignKeyField, targetPrimaryKeyField); |
| getTargetToSourceKeyFields().put(targetPrimaryKeyField, sourceForeignKeyField); |
| } |
| |
| /** |
| * PUBLIC: |
| * Define the foreign key relationship in the 1-1 mapping. |
| * This method is used for composite foreign key relationships, |
| * that is the source object's table has multiple foreign key fields to |
| * the target object's primary key fields. |
| * Both the source foreign key field name and the target foreign key field |
| * name must be specified. |
| * When a foreign key is specified TopLink will automatically populate the |
| * value for that field from the target object when the object is written to |
| * the database. If the foreign key is also mapped through a direct-to-field |
| * then the direct-to-field must be set read-only. |
| */ |
| public void addForeignKeyFieldName(String sourceForeignKeyFieldName, String targetPrimaryKeyFieldName) { |
| addForeignKeyField(new DatabaseField(sourceForeignKeyFieldName), new DatabaseField(targetPrimaryKeyFieldName)); |
| } |
| |
| /** |
| * PUBLIC: |
| * Define the target foreign key relationship in the 1-1 mapping. |
| * This method is used for composite target foreign key relationships, |
| * that is the target object's table has multiple foreign key fields to |
| * the source object's primary key fields. |
| * Both the target foreign key field and the source primary key field must |
| * be specified. |
| * The distinction between a foreign key and target foreign key is that the |
| * 1-1 mapping will not populate the target foreign key value when written |
| * (because it is in the target table). Normally 1-1's are through foreign |
| * keys but in bi-directional 1-1's the back reference will be a target |
| * foreign key. In obscure composite legacy data models a 1-1 may consist of |
| * a foreign key part and a target foreign key part, in this case both |
| * method will be called with the correct parts. |
| */ |
| @Override |
| public void addTargetForeignKeyField(DatabaseField targetForeignKeyField, DatabaseField sourcePrimaryKeyField) { |
| getSourceToTargetKeyFields().put(sourcePrimaryKeyField, targetForeignKeyField); |
| getTargetToSourceKeyFields().put(targetForeignKeyField, sourcePrimaryKeyField); |
| } |
| |
| /** |
| * PUBLIC: |
| * Define the target foreign key relationship in the 1-1 mapping. |
| * This method is used for composite target foreign key relationships, |
| * that is the target object's table has multiple foreign key fields to |
| * the source object's primary key fields. |
| * Both the target foreign key field name and the source primary key field |
| * name must be specified. |
| * The distinction between a foreign key and target foreign key is that the |
| * 1-1 mapping will not populate the target foreign key value when written |
| * (because it is in the target table). Normally 1-1's are through foreign |
| * keys but in bi-directional 1-1's the back reference will be a target |
| * foreign key. In obscure composite legacy data models a 1-1 may consist of |
| * a foreign key part and a target foreign key part, in this case both |
| * method will be called with the correct parts. |
| */ |
| public void addTargetForeignKeyFieldName(String targetForeignKeyFieldName, String sourcePrimaryKeyFieldName) { |
| addTargetForeignKeyField(new DatabaseField(targetForeignKeyFieldName), new DatabaseField(sourcePrimaryKeyFieldName)); |
| } |
| |
| /** |
| * INTERNAL: |
| * For mappings used as MapKeys in MappedKeyContainerPolicy. Add the target of this mapping to the deleted |
| * objects list if necessary |
| * |
| * This method is used for removal of private owned relationships. |
| */ |
| @Override |
| public void addKeyToDeletedObjectsList(Object object, Map deletedObjects){ |
| deletedObjects.put(object, object); |
| } |
| |
| /** |
| * Build a clone of the given element in a unitOfWork. |
| */ |
| @Override |
| public Object buildElementClone(Object attributeValue, Object parent, CacheKey cacheKey, Integer refreshCascade, AbstractSession cloningSession, boolean isExisting, boolean isFromSharedCache){ |
| return buildCloneForPartObject(attributeValue, null, cacheKey, parent, cloningSession, refreshCascade, isExisting, isFromSharedCache); |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to allow object level comparisons. |
| */ |
| @Override |
| public Expression buildObjectJoinExpression(Expression expression, Object value, AbstractSession session) { |
| Expression base = ((ObjectExpression)expression).getBaseExpression(); |
| Expression foreignKeyJoin = null; |
| |
| if(this.mechanism == null) { |
| // Allow for equal null. |
| if (value == null) { |
| if (!isForeignKeyRelationship()) { |
| // ELBug#331352 |
| // Need to do a join and compare target foreign key to null. |
| for (DatabaseField field : getSourceToTargetKeyFields().values()) { |
| Expression join = null; |
| join = expression.getField(field).equal(null); |
| if (foreignKeyJoin == null) { |
| foreignKeyJoin = join; |
| } else { |
| foreignKeyJoin = foreignKeyJoin.and(join); |
| } |
| } |
| } else { |
| for (DatabaseField field : getSourceToTargetKeyFields().keySet()) { |
| Expression join = null; |
| join = base.getField(field).equal(null); |
| if (foreignKeyJoin == null) { |
| foreignKeyJoin = join; |
| } else { |
| foreignKeyJoin = foreignKeyJoin.and(join); |
| } |
| } |
| } |
| } else { |
| if (!getReferenceDescriptor().getJavaClass().isInstance(value)) { |
| // Bug 3894351 - ensure any proxys are triggered so we can do a proper class comparison |
| value = ProxyIndirectionPolicy.getValueFromProxy(value); |
| if (!getReferenceDescriptor().getJavaClass().isInstance(value)) { |
| throw QueryException.incorrectClassForObjectComparison(base, value, this); |
| } |
| } |
| |
| Iterator<Object> keyIterator = Arrays.asList(((CacheId)extractKeyFromReferenceObject(value, session)).getPrimaryKey()).iterator(); |
| for (DatabaseField field : getSourceToTargetKeyFields().keySet()) { |
| Expression join = null; |
| join = base.getField(field).equal(keyIterator.next()); |
| if (foreignKeyJoin == null) { |
| foreignKeyJoin = join; |
| } else { |
| foreignKeyJoin = foreignKeyJoin.and(join); |
| } |
| } |
| } |
| } else { |
| int size = this.mechanism.sourceKeyFields.size(); |
| Object key = null; |
| if (value != null) { |
| if (!getReferenceDescriptor().getJavaClass().isInstance(value)) { |
| // Bug 3894351 - ensure any proxys are triggered so we can do a proper class comparison |
| value = ProxyIndirectionPolicy.getValueFromProxy(value); |
| if (!getReferenceDescriptor().getJavaClass().isInstance(value)) { |
| throw QueryException.incorrectClassForObjectComparison(base, value, this); |
| } |
| } |
| key = extractKeyFromReferenceObject(value, session); |
| boolean allNulls = true; |
| for (int i=0; i < size; i++) { |
| if (((CacheId)key).getPrimaryKey()[i] != null) { |
| allNulls = false; |
| break; |
| } |
| } |
| // the same case |
| if (allNulls) { |
| value = null; |
| } |
| } |
| if (value != null) { |
| for(int i=0; i < size; i++) { |
| DatabaseField field = this.mechanism.sourceKeyFields.get(i); |
| Expression join = null; |
| join = base.getField(field).equal(((CacheId)key).getPrimaryKey()[i]); |
| if (foreignKeyJoin == null) { |
| foreignKeyJoin = join; |
| } else { |
| foreignKeyJoin = foreignKeyJoin.and(join); |
| } |
| } |
| } else { |
| ReportQuery subQuery = new ReportQuery(this.descriptor.getJavaClass(), new ExpressionBuilder()); |
| Expression relationTableExp = subQuery.getExpressionBuilder().getTable(this.mechanism.relationTable); |
| Expression subSelectExp = null; |
| for(int i=0; i < size; i++) { |
| subSelectExp = relationTableExp.getField(this.mechanism.sourceRelationKeyFields.get(i)).equal(base.getField(this.mechanism.sourceKeyFields.get(i))).and(subSelectExp); |
| } |
| subQuery.setSelectionCriteria(subSelectExp); |
| subQuery.dontRetrievePrimaryKeys(); |
| subQuery.addAttribute("", subQuery.getExpressionBuilder().getField(this.mechanism.sourceKeyFields.get(0))); |
| foreignKeyJoin = base.notExists(subQuery); |
| } |
| } |
| return foreignKeyJoin; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to allow object level comparisons. |
| */ |
| @Override |
| public Expression buildObjectJoinExpression(Expression expression, Expression argument, AbstractSession session) { |
| Expression base = ((org.eclipse.persistence.internal.expressions.ObjectExpression)expression).getBaseExpression(); |
| Expression foreignKeyJoin = null; |
| if(this.mechanism == null) { |
| if (expression==argument){ |
| for (Iterator<DatabaseField> sourceFieldsEnum = getSourceToTargetKeyFields().keySet().iterator(); |
| sourceFieldsEnum.hasNext();) { |
| DatabaseField field = sourceFieldsEnum.next(); |
| Expression join = base.getField(field); |
| join = join.equal(join); |
| if (foreignKeyJoin == null) { |
| foreignKeyJoin = join; |
| } else { |
| foreignKeyJoin = foreignKeyJoin.and(join); |
| } |
| } |
| }else{ |
| Iterator<DatabaseField> targetFieldsEnum = getSourceToTargetKeyFields().values().iterator(); |
| for (Iterator<DatabaseField> sourceFieldsEnum = getSourceToTargetKeyFields().keySet().iterator(); |
| sourceFieldsEnum.hasNext();) { |
| DatabaseField sourceField = sourceFieldsEnum.next(); |
| DatabaseField targetField = targetFieldsEnum.next(); |
| Expression join = null; |
| join = base.getField(sourceField).equal(argument.getField(targetField)); |
| if (foreignKeyJoin == null) { |
| foreignKeyJoin = join; |
| } else { |
| foreignKeyJoin = foreignKeyJoin.and(join); |
| } |
| } |
| } |
| } else { |
| if (expression==argument){ |
| foreignKeyJoin = (new ConstantExpression(0, base)).equal(new ConstantExpression(0, base)); |
| }else{ |
| int size = this.mechanism.sourceKeyFields.size(); |
| Expression relTable = base.getTable(this.mechanism.getRelationTable()); |
| for(int i=0; i < size; i++) { |
| Expression source = base.getField(this.mechanism.sourceKeyFields.get(i)); |
| Expression sourceRel = relTable.getField(this.mechanism.sourceRelationKeyFields.get(i)); |
| Expression targetRel = relTable.getField(this.mechanism.targetRelationKeyFields.get(i)); |
| Expression target = argument.getField(this.mechanism.targetKeyFields.get(i)); |
| foreignKeyJoin = source.equal(sourceRel).and(targetRel.equal(target)).and(foreignKeyJoin); |
| } |
| } |
| } |
| return foreignKeyJoin; |
| } |
| |
| /** |
| * INTERNAL: |
| * Certain key mappings favor different types of selection query. Return the appropriate |
| * type of selectionQuery |
| */ |
| @Override |
| public ReadQuery buildSelectionQueryForDirectCollectionKeyMapping(ContainerPolicy containerPolicy){ |
| DataReadQuery query = new DataReadQuery(); |
| query.setSQLStatement(new SQLSelectStatement()); |
| query.setContainerPolicy(containerPolicy); |
| return query; |
| } |
| |
| /** |
| * INTERNAL: |
| * This methods clones all the fields and ensures that each collection refers to |
| * the same clones. |
| */ |
| @Override |
| public Object clone() { |
| OneToOneMapping clone = (OneToOneMapping)super.clone(); |
| if(this.mechanism == null) { |
| clone.setForeignKeyFields(org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(getForeignKeyFields().size())); |
| clone.setSourceToTargetKeyFields(new HashMap(getSourceToTargetKeyFields().size())); |
| clone.setTargetToSourceKeyFields(new HashMap(getTargetToSourceKeyFields().size())); |
| |
| // DatabaseField overrides equals, so we need to use an IdentifyHashMap for this implementation |
| // to make sure we are using the object references to lookup clone references |
| AbstractMap<DatabaseField, DatabaseField> fieldToCloneMap = new IdentityHashMap<>(getTargetToSourceKeyFields().size()); |
| |
| //clone foreign keys and save the clones in a lookup table |
| for (Enumeration<DatabaseField> enumtr = getForeignKeyFields().elements(); enumtr.hasMoreElements();) { |
| DatabaseField field = enumtr.nextElement(); |
| DatabaseField fieldClone = field.clone(); |
| fieldToCloneMap.put(field, fieldClone); |
| clone.getForeignKeyFields().addElement(fieldClone); |
| } |
| |
| // lookup references in the map to get the associated clone reference. If it doesn't exist, create a new one. |
| for (Iterator<DatabaseField> sourceEnum = getSourceToTargetKeyFields().keySet().iterator(); |
| sourceEnum.hasNext();) { |
| DatabaseField sourceField = sourceEnum.next(); |
| DatabaseField targetField = getSourceToTargetKeyFields().get(sourceField); |
| |
| DatabaseField targetClone; |
| DatabaseField sourceClone; |
| |
| targetClone = fieldToCloneMap.get(targetField); |
| if (targetClone == null) { |
| targetClone = targetField.clone(); |
| fieldToCloneMap.put(targetField, targetClone); |
| } |
| sourceClone = fieldToCloneMap.get(sourceField); |
| if (sourceClone == null) { |
| sourceClone = sourceField.clone(); |
| fieldToCloneMap.put(sourceField, sourceClone); |
| } |
| clone.getSourceToTargetKeyFields().put(sourceClone, targetClone); |
| } |
| |
| // lookup references in the map to get the associated clone reference. If it doesn't exist, create a new one. |
| for (Iterator<DatabaseField> targetEnum = getTargetToSourceKeyFields().keySet().iterator(); |
| targetEnum.hasNext();) { |
| DatabaseField targetField = targetEnum.next(); |
| DatabaseField sourceField = getTargetToSourceKeyFields().get(targetField); |
| |
| DatabaseField targetClone; |
| DatabaseField sourceClone; |
| |
| targetClone = fieldToCloneMap.get(targetField); |
| if (targetClone == null) { |
| targetClone = targetField.clone(); |
| fieldToCloneMap.put(targetField, targetClone); |
| } |
| sourceClone = fieldToCloneMap.get(sourceField); |
| if (sourceClone == null) { |
| sourceClone = sourceField.clone(); |
| fieldToCloneMap.put(sourceField, sourceClone); |
| } |
| clone.getTargetToSourceKeyFields().put(targetClone, sourceClone); |
| } |
| } else { |
| clone.mechanism = (RelationTableMechanism)this.mechanism.clone(); |
| } |
| return clone; |
| } |
| |
| @Override |
| public void collectQueryParameters(Set<DatabaseField> cacheFields){ |
| for (DatabaseField field : sourceToTargetKeyFields.keySet()) { |
| cacheFields.add(field); |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Called when a DatabaseMapping is used to map the key in a collection. Returns the key. |
| */ |
| @Override |
| public Object createMapComponentFromRow(AbstractRecord dbRow, ObjectBuildingQuery query, CacheKey parentCacheKey, AbstractSession session, boolean isTargetProtected){ |
| return session.executeQuery(getSelectionQuery(), dbRow); |
| } |
| |
| /** |
| * INTERNAL: |
| * Creates the Array of simple types used to recreate this map. |
| */ |
| @Override |
| public Object createSerializableMapKeyInfo(Object key, AbstractSession session){ |
| return referenceDescriptor.getObjectBuilder().extractPrimaryKeyFromObject(key, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Create an instance of the Key object from the key information extracted from the map. |
| * This may return the value directly in case of a simple key or will be used as the FK to load a related entity. |
| */ |
| @Override |
| public List<Object> createMapComponentsFromSerializableKeyInfo(Object[] keyInfo, AbstractSession session){ |
| List<Object> orderedResult = new ArrayList<>(keyInfo.length); |
| Map<Object, Object> fromCache = session.getIdentityMapAccessorInstance().getAllFromIdentityMapWithEntityPK(keyInfo, referenceDescriptor); |
| List foreignKeyValues = new ArrayList(keyInfo.length - fromCache.size()); |
| |
| CacheKeyType cacheKeyType = referenceDescriptor.getCachePolicy().getCacheKeyType(); |
| for (int index = 0; index < keyInfo.length; ++index){ |
| Object pk = keyInfo[index]; |
| if (!fromCache.containsKey(pk)){ |
| if (cacheKeyType == CacheKeyType.CACHE_ID){ |
| foreignKeyValues.add(Arrays.asList(((CacheId)pk).getPrimaryKey())); |
| }else{ |
| foreignKeyValues.add(pk); |
| } |
| } |
| } |
| if (!foreignKeyValues.isEmpty()){ |
| ReadAllQuery query = new ReadAllQuery(referenceDescriptor.getJavaClass()); |
| query.setIsExecutionClone(true); |
| query.addArgument(ForeignReferenceMapping.QUERY_BATCH_PARAMETER); |
| query.addArgumentValue(foreignKeyValues); |
| query.setSession(session); |
| query.setSelectionCriteria(referenceDescriptor.buildBatchCriteriaByPK(query.getExpressionBuilder(), query)); |
| Collection<Object> temp = (Collection<Object>) session.executeQuery(query); |
| for (Object element: temp){ |
| Object pk = referenceDescriptor.getObjectBuilder().extractPrimaryKeyFromObject(element, session); |
| fromCache.put(pk, element); |
| } |
| } |
| for(Object key : keyInfo){ |
| orderedResult.add(fromCache.get(key)); |
| } |
| return orderedResult; |
| } |
| |
| /** |
| * INTERNAL: |
| * Create an instance of the Key object from the key information extracted from the map. |
| * This key object may be a shallow stub of the actual object if the key is an Entity type. |
| */ |
| @Override |
| public Object createStubbedMapComponentFromSerializableKeyInfo(Object keyInfo, AbstractSession session) { |
| ObjectBuilder builder = this.referenceDescriptor.getObjectBuilder(); |
| ObjectBuildingQuery clonedQuery = (ObjectBuildingQuery) getSelectionQuery().clone(); |
| clonedQuery.setSession(session); |
| Object newObject = referenceDescriptor.getInstantiationPolicy().buildNewInstance(); |
| builder.buildPrimaryKeyAttributesIntoObject(newObject, builder.buildRowFromPrimaryKeyValues(keyInfo, session), clonedQuery, session); |
| return newObject; |
| } |
| |
| /** |
| * INTERNAL |
| * Called when a DatabaseMapping is used to map the key in a collection. Returns the key. |
| */ |
| @Override |
| public Object createMapComponentFromJoinedRow(AbstractRecord dbRow, JoinedAttributeManager joinManager, ObjectBuildingQuery query, CacheKey parentCacheKey, AbstractSession session, boolean isTargetProtected){ |
| return valueFromRowInternalWithJoin(dbRow, joinManager, query, parentCacheKey, session, isTargetProtected); |
| } |
| |
| /** |
| * INTERNAL: |
| * Create a query key that links to the map key |
| */ |
| @Override |
| public QueryKey createQueryKeyForMapKey(){ |
| OneToOneQueryKey key = new OneToOneQueryKey(); |
| key.setDescriptor(getReferenceDescriptor()); |
| key.setReferenceClass(getReferenceClass()); |
| key.setJoinCriteria(getAdditionalSelectionCriteriaForMapKey()); |
| return key; |
| } |
| |
| /** |
| * INTERNAL: |
| * For mappings used as MapKeys in MappedKeyContainerPolicy, Delete the passed object if necessary. |
| * |
| * This method is used for removal of private owned relationships |
| * |
| */ |
| @Override |
| public void deleteMapKey(Object objectDeleted, AbstractSession session){ |
| session.deleteObject(objectDeleted); |
| } |
| |
| /** |
| * INTERNAL: |
| * Adds locking clause to the target query to extend pessimistic lock scope. |
| */ |
| @Override |
| protected void extendPessimisticLockScopeInTargetQuery(ObjectLevelReadQuery targetQuery, ObjectBuildingQuery sourceQuery) { |
| if(this.mechanism == null) { |
| super.extendPessimisticLockScopeInTargetQuery(targetQuery, sourceQuery); |
| } else { |
| this.mechanism.setRelationTableLockingClause(targetQuery, sourceQuery); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Called only if both |
| * shouldExtendPessimisticLockScope and shouldExtendPessimisticLockScopeInSourceQuery are true. |
| * Adds fields to be locked to the where clause of the source query. |
| * Note that the sourceQuery must be ObjectLevelReadQuery so that it has ExpressionBuilder. |
| * |
| * This method must be implemented in subclasses that allow |
| * setting shouldExtendPessimisticLockScopeInSourceQuery to true. |
| */ |
| @Override |
| public void extendPessimisticLockScopeInSourceQuery(ObjectLevelReadQuery sourceQuery) { |
| Expression exp = sourceQuery.getSelectionCriteria(); |
| if(this.mechanism == null) { |
| ExpressionBuilder builder = sourceQuery.getExpressionBuilder(); |
| Iterator<Map.Entry<DatabaseField, DatabaseField>> it = this.getSourceToTargetKeyFields().entrySet().iterator(); |
| Map.Entry<DatabaseField, DatabaseField> entry = it.next(); |
| exp = builder.getField(entry.getKey()).equal(builder.get(this.getAttributeName()).getField(entry.getValue())).and(exp); |
| } else { |
| exp = this.mechanism.joinRelationTableField(exp, sourceQuery.getExpressionBuilder()); |
| } |
| sourceQuery.setSelectionCriteria(exp); |
| } |
| |
| /** |
| * INTERNAL: |
| * Extract the foreign key value from the source row. |
| */ |
| @Override |
| protected Object extractBatchKeyFromRow(AbstractRecord row, AbstractSession session) { |
| if (this.mechanism != null) { |
| return this.mechanism.extractBatchKeyFromRow(row, session); |
| } |
| Object[] key; |
| ConversionManager conversionManager = session.getDatasourcePlatform().getConversionManager(); |
| key = new Object[this.sourceToTargetKeyFields.size()]; |
| int index = 0; |
| for (DatabaseField field : this.sourceToTargetKeyFields.keySet()) { |
| Object value = row.get(field); |
| if (value == null) { |
| return null; |
| } |
| // Must ensure the classification gets a cache hit. |
| try { |
| value = conversionManager.convertObject(value, field.getType()); |
| } catch (ConversionException exception) { |
| throw ConversionException.couldNotBeConverted(this, this.descriptor, exception); |
| } |
| key[index] = value; |
| index++; |
| } |
| return new CacheId(key); |
| } |
| |
| /** |
| * INTERNAL: |
| * Extract the fields for the Map key from the object to use in a query |
| */ |
| @Override |
| public Map extractIdentityFieldsForQuery(Object object, AbstractSession session){ |
| Map keyFields = new HashMap(); |
| for (int index = 0; index < getForeignKeyFields().size(); index++) { |
| DatabaseField targetRelationField = getForeignKeyFields().elementAt(index); |
| DatabaseField targetKey = getSourceToTargetKeyFields().get(targetRelationField); |
| Object value = getReferenceDescriptor().getObjectBuilder().extractValueFromObjectForField(object, targetKey, session); |
| keyFields.put(targetRelationField, value); |
| } |
| return keyFields; |
| } |
| |
| /** |
| * INTERNAL: |
| * Extract the key value from the reference object. |
| */ |
| protected Object extractKeyFromReferenceObject(Object object, AbstractSession session) { |
| ObjectBuilder objectBuilder = getReferenceDescriptor().getObjectBuilder(); |
| Object[] key; |
| if (this.mechanism == null) { |
| key = new Object[getSourceToTargetKeyFields().size()]; |
| int index = 0; |
| for (DatabaseField field : getSourceToTargetKeyFields().values()) { |
| if (object == null) { |
| key[index] = null; |
| } else { |
| key[index] = objectBuilder.extractValueFromObjectForField(object, field, session); |
| } |
| index++; |
| } |
| } else { |
| int size = this.mechanism.targetKeyFields.size(); |
| key = new Object[size]; |
| for (int i = 0; i < size; i++) { |
| if (object == null) { |
| key[i] = null; |
| } else { |
| DatabaseField field = this.mechanism.targetKeyFields.get(i); |
| key[i] = objectBuilder.extractValueFromObjectForField(object, field, session); |
| } |
| } |
| } |
| |
| return new CacheId(key); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the primary key for the reference object (i.e. the object |
| * object referenced by domainObject and specified by mapping). |
| * This key will be used by a RemoteValueHolder. |
| */ |
| @Override |
| public Object extractPrimaryKeysForReferenceObjectFromRow(AbstractRecord row) { |
| List<DatabaseField> primaryKeyFields = getReferenceDescriptor().getPrimaryKeyFields(); |
| Object[] result = new Object[primaryKeyFields.size()]; |
| for (int index = 0; index < primaryKeyFields.size(); index++) { |
| DatabaseField targetKeyField = primaryKeyFields.get(index); |
| DatabaseField sourceKeyField = getTargetToSourceKeyFields().get(targetKeyField); |
| if (sourceKeyField == null) { |
| return null; |
| } |
| result[index] = row.get(sourceKeyField); |
| if (getReferenceDescriptor().getCachePolicy().getCacheKeyType() == CacheKeyType.ID_VALUE) { |
| return result[index]; |
| } |
| } |
| return new CacheId(result); |
| } |
| |
| /** |
| * INTERNAL: |
| * Allow the mapping the do any further batch preparation. |
| */ |
| @Override |
| protected void postPrepareNestedBatchQuery(ReadQuery batchQuery, ObjectLevelReadQuery query) { |
| super.postPrepareNestedBatchQuery(batchQuery, query); |
| // Force a distinct to filter out m-1 duplicates. |
| // Only set if really a m-1, not a 1-1 |
| if (!this.isOneToOneRelationship && ((ObjectLevelReadQuery)batchQuery).getBatchFetchPolicy().isJOIN()) { |
| if (!((ObjectLevelReadQuery)batchQuery).isDistinctComputed() && (batchQuery.getSession().getPlatform().isLobCompatibleWithDistinct() || !Helper.hasLob(batchQuery.getDescriptor().getSelectionFields((ObjectLevelReadQuery)batchQuery)))) { |
| ((ObjectLevelReadQuery)batchQuery).useDistinct(); |
| } |
| } |
| if (this.mechanism != null) { |
| this.mechanism.postPrepareNestedBatchQuery(batchQuery, query); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the selection criteria used to IN batch fetching. |
| */ |
| @Override |
| protected Expression buildBatchCriteria(ExpressionBuilder builder, ObjectLevelReadQuery query) { |
| if (this.mechanism == null) { |
| int size = this.sourceToTargetKeyFields.size(); |
| if (size > 1) { |
| // Support composite keys using nested IN. |
| List<Expression> fields = new ArrayList<>(size); |
| for (DatabaseField targetForeignKeyField : this.sourceToTargetKeyFields.values()) { |
| fields.add(builder.getField(targetForeignKeyField)); |
| } |
| return query.getSession().getPlatform().buildBatchCriteriaForComplexId(builder, fields); |
| } else { |
| return query.getSession().getPlatform().buildBatchCriteria(builder, builder.getField(this.sourceToTargetKeyFields.values().iterator().next())); |
| } |
| } else { |
| return this.mechanism.buildBatchCriteria(builder, query); |
| } |
| } |
| |
| /** |
| * 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 referenceObjectsByKey, AbstractSession session, AbstractRecord translationRow) { |
| // Execute query and index resulting objects by key. |
| List results; |
| ObjectBuilder builder = query.getDescriptor().getObjectBuilder(); |
| if (this.mechanism == null) { |
| results = (List)session.executeQuery(query, translationRow); |
| for (Object eachReferenceObject : results) { |
| Object eachReferenceKey = extractKeyFromReferenceObject(eachReferenceObject, session); |
| referenceObjectsByKey.put(eachReferenceKey, builder.wrapObject(eachReferenceObject, session)); |
| } |
| } else { |
| ComplexQueryResult complexResult = (ComplexQueryResult)session.executeQuery(query, translationRow); |
| results = (List)complexResult.getResult(); |
| List<AbstractRecord> rows = (List)complexResult.getData(); |
| int size = results.size(); |
| for (int index = 0; index < size; index++) { |
| AbstractRecord row = rows.get(index); |
| Object key = this.mechanism.extractKeyFromTargetRow(row, session); |
| referenceObjectsByKey.put(key, builder.wrapObject(results.get(index), session)); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Check if the target object is in the cache if possible based on the target key value. |
| * Return null if the target key is not the primary key, or if the query is refreshing. |
| */ |
| @Override |
| protected Object checkCacheForBatchKey(AbstractRecord sourceRow, Object foreignKey, Map batchObjects, ReadQuery batchQuery, ObjectLevelReadQuery originalQuery, AbstractSession session) { |
| if (((ReadAllQuery)batchQuery).shouldRefreshIdentityMapResult() || (!batchQuery.shouldMaintainCache())) { |
| return null; |
| } |
| // Check the cache using the source row and selection query. |
| Object cachedObject = this.selectionQuery.checkEarlyReturn(session, sourceRow); |
| if ((cachedObject != null) && (batchObjects != null)) { |
| batchObjects.put(foreignKey, cachedObject); |
| } |
| return cachedObject; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the selection criteria necessary to select the target object when this mapping |
| * is a map key. |
| */ |
| @Override |
| public Expression getAdditionalSelectionCriteriaForMapKey(){ |
| return buildSelectionCriteria(false, false); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return any tables that will be required when this mapping is used as part of a join query |
| */ |
| @Override |
| public List<DatabaseTable> getAdditionalTablesForJoinQuery(){ |
| List<DatabaseTable> tables = new ArrayList<>(getReferenceDescriptor().getTables().size() + 1); |
| tables.addAll(getReferenceDescriptor().getTables()); |
| if (keyTableForMapKey != null){ |
| tables.add(keyTableForMapKey); |
| } |
| return tables; |
| } |
| |
| /** |
| * INTERNAL: |
| * Should be overridden by subclass that allows setting |
| * extendPessimisticLockScope to DEDICATED_QUERY. |
| */ |
| @Override |
| protected ReadQuery getExtendPessimisticLockScopeDedicatedQuery(AbstractSession session, short lockMode) { |
| if(this.mechanism != null) { |
| return this.mechanism.getLockRelationTableQueryClone(session, lockMode); |
| } else { |
| return super.getExtendPessimisticLockScopeDedicatedQuery(session, lockMode); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the classification for the field contained in the mapping. |
| * This is used to convert the row value to a consistent java value. |
| */ |
| @Override |
| public Class<?> getFieldClassification(DatabaseField fieldToClassify) throws DescriptorException { |
| DatabaseField fieldInTarget = getSourceToTargetKeyFields().get(fieldToClassify); |
| if (fieldInTarget == null) { |
| return null;// Can be registered as multiple table secondary field mapping |
| } |
| DatabaseMapping mapping = getReferenceDescriptor().getObjectBuilder().getMappingForField(fieldInTarget); |
| if (mapping == null) { |
| return null;// Means that the mapping is read-only |
| } |
| return mapping.getFieldClassification(fieldInTarget); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the foreign key field names associated with the mapping. |
| * These are only the source fields that are writable. |
| */ |
| public Vector getForeignKeyFieldNames() { |
| Vector fieldNames = new Vector(getForeignKeyFields().size()); |
| for (Enumeration<DatabaseField> fieldsEnum = getForeignKeyFields().elements(); |
| fieldsEnum.hasMoreElements();) { |
| fieldNames.addElement(fieldsEnum.nextElement().getQualifiedName()); |
| } |
| |
| return fieldNames; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return source key fields for translation by an AggregateObjectMapping |
| */ |
| @Override |
| public Collection getFieldsForTranslationInAggregate() { |
| return getSourceToTargetKeyFields().keySet(); |
| } |
| |
| /** |
| * Return the appropriate map that maps the "foreign keys" |
| * to the "primary keys". |
| */ |
| protected Map getForeignKeysToPrimaryKeys() { |
| if (this.isForeignKeyRelationship()) { |
| return this.getSourceToTargetKeyFields(); |
| } else { |
| return this.getTargetToSourceKeyFields(); |
| } |
| } |
| |
| |
| /** |
| * INTERNAL: |
| * Return a Map of any foreign keys defined within the the MapKey |
| */ |
| @Override |
| public Map<DatabaseField, DatabaseField> getForeignKeyFieldsForMapKey(){ |
| return getSourceToTargetKeyFields(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the fields that make up the identity of the mapped object. For mappings with |
| * a primary key, it will be the set of fields in the primary key. For mappings without |
| * a primary key it will likely be all the fields |
| */ |
| @Override |
| public List<DatabaseField> getIdentityFieldsForMapKey(){ |
| return getForeignKeyFields(); |
| } |
| |
| |
| /** |
| * INTERNAL: |
| * Return the query that is used when this mapping is part of a joined relationship |
| * |
| * This method is used when this mapping is used to map the key in a Map |
| */ |
| @Override |
| public ObjectLevelReadQuery getNestedJoinQuery(JoinedAttributeManager joinManager, ObjectLevelReadQuery query, AbstractSession session){ |
| return prepareNestedJoins(joinManager, query, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Get all the fields for the map key |
| */ |
| @Override |
| public List<DatabaseField> getAllFieldsForMapKey(){ |
| List<DatabaseField> fields = new ArrayList(getReferenceDescriptor().getAllSelectionFields().size() + getForeignKeyFields().size()); |
| fields.addAll(getReferenceDescriptor().getAllSelectionFields()); |
| fields.addAll(getForeignKeyFields()); |
| return fields; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return a vector of the foreign key fields in the same order |
| * as the corresponding primary key fields are in their descriptor. |
| */ |
| public Vector getOrderedForeignKeyFields() { |
| List<DatabaseField> primaryKeyFields = getPrimaryKeyDescriptor().getPrimaryKeyFields(); |
| Vector result = new Vector(primaryKeyFields.size()); |
| |
| for (int index = 0; index < primaryKeyFields.size(); index++) { |
| DatabaseField pkField = primaryKeyFields.get(index); |
| boolean found = false; |
| for (Iterator fkStream = this.getForeignKeysToPrimaryKeys().keySet().iterator(); |
| fkStream.hasNext();) { |
| DatabaseField fkField = (DatabaseField)fkStream.next(); |
| |
| if (this.getForeignKeysToPrimaryKeys().get(fkField).equals(pkField)) { |
| found = true; |
| result.addElement(fkField); |
| break; |
| } |
| } |
| if (!found) { |
| throw DescriptorException.missingForeignKeyTranslation(this, pkField); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Return the descriptor for whichever side of the |
| * relation has the "primary key". |
| */ |
| protected ClassDescriptor getPrimaryKeyDescriptor() { |
| if (this.isForeignKeyRelationship()) { |
| return this.getReferenceDescriptor(); |
| } else { |
| return this.getDescriptor(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * The private owned criteria is only used outside of the unit of work to compare the previous value of the reference. |
| */ |
| public Expression getPrivateOwnedCriteria() { |
| if (privateOwnedCriteria == null) { |
| initializePrivateOwnedCriteria(); |
| } |
| return privateOwnedCriteria; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return a collection of the source to target field value associations. |
| */ |
| public Vector getSourceToTargetKeyFieldAssociations() { |
| Vector associations = new Vector(getSourceToTargetKeyFields().size()); |
| Iterator<DatabaseField> sourceFieldEnum = getSourceToTargetKeyFields().keySet().iterator(); |
| Iterator<DatabaseField> targetFieldEnum = getSourceToTargetKeyFields().values().iterator(); |
| while (sourceFieldEnum.hasNext()) { |
| Object fieldValue = sourceFieldEnum.next().getQualifiedName(); |
| Object attributeValue = targetFieldEnum.next().getQualifiedName(); |
| associations.addElement(new Association(fieldValue, attributeValue)); |
| } |
| |
| return associations; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the source keys to target keys fields association. |
| */ |
| public Map<DatabaseField, DatabaseField> getSourceToTargetKeyFields() { |
| return sourceToTargetKeyFields; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the target keys to source keys fields association. |
| */ |
| public Map<DatabaseField, DatabaseField> getTargetToSourceKeyFields() { |
| return targetToSourceKeyFields; |
| } |
| |
| /** |
| * INTERNAL: |
| * If required, get the targetVersion of the source object from the merge manager |
| * |
| * Used with MapKeyContainerPolicy to abstract getting the target version of a source key |
| */ |
| @Override |
| public Object getTargetVersionOfSourceObject(Object object, Object parent, MergeManager mergeManager, AbstractSession targetSession){ |
| return mergeManager.getTargetVersionOfSourceObject(object, referenceDescriptor, targetSession); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the class this key mapping maps or the descriptor for it |
| */ |
| @Override |
| public Class<?> getMapKeyTargetType(){ |
| return getReferenceClass(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Initialize the mapping. |
| */ |
| @Override |
| public void initialize(AbstractSession session) throws DescriptorException { |
| if (session.hasBroker()) { |
| if (getReferenceClass() == null) { |
| throw DescriptorException.referenceClassNotSpecified(this); |
| } |
| // substitute session that owns the mapping for the session that owns reference descriptor. |
| session = session.getBroker().getSessionForClass(getReferenceClass()); |
| } |
| |
| super.initialize(session); |
| if (isForeignKeyRelationship() && !isMapKeyMapping()) { |
| getDescriptor().addPreDeleteMapping(this); |
| } |
| |
| // Capture our foreign key field specifications here. We need to build |
| // the fields first to ensure they have a table associated with them. |
| // Also must be careful to not set the flags based on a previously |
| // built field (multiple mappings to the same field) since we need to |
| // capture the flags from the field set directly on this mapping. |
| for (DatabaseField field : getForeignKeyFields()) { |
| DatabaseField builtField = getDescriptor().buildField(field, keyTableForMapKey); |
| |
| if (builtField == field || builtField.isTranslated()) { |
| // same instance or translated, look at the built field. |
| updateInsertableAndUpdatableFields(builtField); |
| } else { |
| // previously built field and not translated, look at the original field. |
| updateInsertableAndUpdatableFields(field); |
| } |
| } |
| |
| if (this.mechanism != null) { |
| if (this.mechanism.hasRelationTable()) { |
| if(!this.foreignKeyFields.isEmpty() || !this.sourceToTargetKeyFields.isEmpty() || !this.targetToSourceKeyFields.isEmpty()) { |
| throw DescriptorException.oneToOneMappingConflict(this.getDescriptor(), this); |
| } |
| this.foreignKeyFields = null; |
| this.sourceToTargetKeyFields = null; |
| this.targetToSourceKeyFields = null; |
| |
| this.mechanism.initialize(session, this); |
| } else { |
| this.mechanism = null; |
| } |
| } |
| |
| if (this.mechanism == null) { |
| // Must set table of foreign keys. |
| for (int index = 0; index < getForeignKeyFields().size(); index++) { |
| DatabaseField foreignKeyField = getForeignKeyFields().get(index); |
| foreignKeyField = getDescriptor().buildField(foreignKeyField, keyTableForMapKey); |
| getForeignKeyFields().set(index, foreignKeyField); |
| } |
| |
| // If only a selection criteria is specified then the foreign keys do not have to be initialized. |
| if (!(getTargetToSourceKeyFields().isEmpty() && getSourceToTargetKeyFields().isEmpty())) { |
| if (getTargetToSourceKeyFields().isEmpty() || getSourceToTargetKeyFields().isEmpty()) { |
| initializeForeignKeysWithDefaults(session); |
| } else { |
| initializeForeignKeys(session); |
| } |
| } |
| |
| // Check if any foreign keys reference a secondary table. |
| if (getReferenceDescriptor().getTables().size() > 1) { |
| DatabaseTable firstTable = getReferenceDescriptor().getTables().get(0); |
| for (DatabaseField field : getSourceToTargetKeyFields().values()) { |
| if (!field.getTable().equals(firstTable)) { |
| getReferenceDescriptor().setHasMultipleTableConstraintDependecy(true); |
| } |
| } |
| } |
| |
| // Check if any foreign keys reference a secondary table. |
| if (getDescriptor().getTables().size() > 1) { |
| DatabaseTable firstTable = getDescriptor().getTables().get(0); |
| for (DatabaseField field : getSourceToTargetKeyFields().keySet()) { |
| if (!field.getTable().equals(firstTable)) { |
| getDescriptor().setHasMultipleTableConstraintDependecy(true); |
| } |
| } |
| } |
| } |
| |
| if (shouldInitializeSelectionCriteria()) { |
| if (shouldForceInitializationOfSelectionCriteria()) { |
| setSelectionCriteria(buildSelectionCriteria()); |
| } else { |
| setSelectionCriteria(buildSelectionCriteria(true, true)); |
| } |
| } else { |
| setShouldVerifyDelete(false); |
| } |
| |
| setFields(collectFields()); |
| } |
| |
| /** |
| * INTERNAL: |
| * The foreign keys primary keys are stored as database fields in the map. |
| */ |
| protected void initializeForeignKeys(AbstractSession session) { |
| HashMap<DatabaseField, DatabaseField> newSourceToTargetKeyFields = new HashMap(getSourceToTargetKeyFields().size()); |
| HashMap<DatabaseField, DatabaseField> newTargetToSourceKeyFields = new HashMap(getTargetToSourceKeyFields().size()); |
| Iterator<Map.Entry<DatabaseField, DatabaseField>> iterator = getSourceToTargetKeyFields().entrySet().iterator(); |
| while (iterator.hasNext()) { |
| Map.Entry<DatabaseField, DatabaseField> entry = iterator.next(); |
| DatabaseField sourceField = entry.getKey(); |
| sourceField = getDescriptor().buildField(sourceField, keyTableForMapKey); |
| if (usesIndirection()) { |
| sourceField.setKeepInRow(true); |
| } |
| DatabaseField targetField = entry.getValue(); |
| targetField = getReferenceDescriptor().buildField(targetField, keyTableForMapKey); |
| newSourceToTargetKeyFields.put(sourceField, targetField); |
| newTargetToSourceKeyFields.put(targetField, sourceField); |
| } |
| setSourceToTargetKeyFields(newSourceToTargetKeyFields); |
| setTargetToSourceKeyFields(newTargetToSourceKeyFields); |
| } |
| |
| /** |
| * INTERNAL: |
| * The foreign keys primary keys are stored as database fields in the map. |
| */ |
| protected void initializeForeignKeysWithDefaults(AbstractSession session) { |
| if (isForeignKeyRelationship()) { |
| if (getSourceToTargetKeyFields().size() != 1) { |
| throw DescriptorException.foreignKeysDefinedIncorrectly(this); |
| } |
| List<DatabaseField> targetKeys = getReferenceDescriptor().getPrimaryKeyFields(); |
| if (targetKeys.size() != 1) { |
| //target and source keys are not the same size. |
| throw DescriptorException.sizeMismatchOfForeignKeys(this); |
| } |
| |
| //grab the only element out of the map |
| DatabaseField sourceField = getSourceToTargetKeyFields().keySet().iterator().next(); |
| sourceField = getDescriptor().buildField(sourceField); |
| if (usesIndirection()) { |
| sourceField.setKeepInRow(true); |
| } |
| getSourceToTargetKeyFields().clear(); |
| getTargetToSourceKeyFields().clear(); |
| getSourceToTargetKeyFields().put(sourceField, targetKeys.get(0)); |
| getTargetToSourceKeyFields().put(targetKeys.get(0), sourceField); |
| } else { |
| if (getTargetToSourceKeyFields().size() != 1) { |
| throw DescriptorException.foreignKeysDefinedIncorrectly(this); |
| } |
| List<DatabaseField> sourceKeys = getDescriptor().getPrimaryKeyFields(); |
| if (sourceKeys.size() != 1) { |
| //target and source keys are not the same size. |
| throw DescriptorException.sizeMismatchOfForeignKeys(this); |
| } |
| |
| //grab the only element out of the map |
| DatabaseField targetField = getTargetToSourceKeyFields().keySet().iterator().next(); |
| targetField = getReferenceDescriptor().buildField(targetField); |
| getSourceToTargetKeyFields().clear(); |
| getTargetToSourceKeyFields().clear(); |
| getTargetToSourceKeyFields().put(targetField, sourceKeys.get(0)); |
| getSourceToTargetKeyFields().put(sourceKeys.get(0), targetField); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Selection criteria is created with source foreign keys and target keys. |
| */ |
| protected void initializePrivateOwnedCriteria() { |
| if (!isForeignKeyRelationship()) { |
| setPrivateOwnedCriteria(getSelectionCriteria()); |
| } else { |
| Expression pkCriteria = getDescriptor().getObjectBuilder().getPrimaryKeyExpression(); |
| ExpressionBuilder builder = new ExpressionBuilder(); |
| Expression backRef = builder.getManualQueryKey(getAttributeName() + "-back-ref", getDescriptor()); |
| Expression newPKCriteria = pkCriteria.rebuildOn(backRef); |
| Expression twistedSelection = backRef.twist(getSelectionCriteria(), builder); |
| if (getDescriptor().getQueryManager().getAdditionalJoinExpression() != null) { |
| // We don't have to twist the additional join because it's all against the same node, which is our base |
| // but we do have to rebuild it onto the manual query key |
| Expression rebuiltAdditional = getDescriptor().getQueryManager().getAdditionalJoinExpression().rebuildOn(backRef); |
| if (twistedSelection == null) { |
| twistedSelection = rebuiltAdditional; |
| } else { |
| twistedSelection = twistedSelection.and(rebuiltAdditional); |
| } |
| } |
| setPrivateOwnedCriteria(newPKCriteria.and(twistedSelection)); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Making any mapping changes necessary to use a the mapping as a map key prior to initializing the mapping |
| */ |
| @Override |
| public void preinitializeMapKey(DatabaseTable table) throws DescriptorException { |
| keyTableForMapKey = table; |
| } |
| |
| /** |
| * INTERNAL: |
| * Need to set the field type for the foreign key fields for a map key, as the fields are not contained in any descriptor. |
| */ |
| @Override |
| public void postInitializeMapKey(MappedKeyMapContainerPolicy policy) { |
| for (DatabaseField foreignKey : getSourceToTargetKeyFields().keySet()) { |
| if (foreignKey.getType() == null) { |
| foreignKey.setType(getFieldClassification(foreignKey)); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Post-initialize source and target expression fields created when a mapping's selectionCriteria |
| * is created early with only partly initialized fields. |
| */ |
| @Override |
| public void postInitializeSourceAndTargetExpressions() { |
| // EL Bug 426500 |
| // postInitialize and set source expression fields using my descriptor |
| if (this.sourceExpressionsToPostInitialize != null && this.sourceExpressionsToPostInitialize.size() > 0) { |
| ClassDescriptor descriptor = getDescriptor(); |
| ObjectBuilder objectBuilder = descriptor.getObjectBuilder(); |
| for (Iterator<Expression> expressions = this.sourceExpressionsToPostInitialize.iterator(); expressions.hasNext();) { |
| Expression expression = expressions.next(); |
| DatabaseField field = null; |
| if (expression.isParameterExpression()) { |
| field = ((ParameterExpression)expression).getField(); |
| } else if (expression.isFieldExpression()) { |
| field = ((FieldExpression)expression).getField(); |
| } |
| if (field != null && (field.getType() == null || field.getTypeName() == null)) { |
| field.setType(objectBuilder.getFieldClassification(field)); |
| } |
| } |
| } |
| |
| // postInitialize and set target expression fields using my reference descriptor |
| if (this.targetExpressionsToPostInitialize != null && this.targetExpressionsToPostInitialize.size() > 0) { |
| ClassDescriptor descriptor = getReferenceDescriptor(); |
| ObjectBuilder objectBuilder = descriptor.getObjectBuilder(); |
| for (Iterator<Expression> expressions = this.targetExpressionsToPostInitialize.iterator(); expressions.hasNext();) { |
| Expression expression = expressions.next(); |
| DatabaseField field = null; |
| if (expression.isParameterExpression()) { |
| field = ((ParameterExpression)expression).getField(); |
| } else if (expression.isFieldExpression()) { |
| field = ((FieldExpression)expression).getField(); |
| } |
| if (field != null && (field.getType() == null || field.getTypeName() == null)) { |
| field.setType(objectBuilder.getFieldClassification(field)); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare a cascade locking policy. |
| */ |
| @Override |
| public void prepareCascadeLockingPolicy() { |
| CascadeLockingPolicy policy = new CascadeLockingPolicy(getDescriptor(), getReferenceDescriptor()); |
| policy.setQueryKeyFields(getSourceToTargetKeyFields(), ! isForeignKeyRelationship()); |
| getReferenceDescriptor().addCascadeLockingPolicy(policy); |
| } |
| |
| /** |
| * This method would allow customers to get the potential selection criteria for a mapping |
| * prior to initialization. This would allow them to more easily create an amendment method |
| * that would amend the SQL for the join. |
| */ |
| public Expression buildSelectionCriteria() { |
| return buildSelectionCriteria(true, false); |
| } |
| |
| /** |
| * INTERNAL: |
| * Build the selection criteria for this mapping. Allows several variations. |
| * |
| * Either a parameter can be used for the join or simply the database field |
| * |
| * The existing selection criteria can be built upon or a whole new criteria can be built. |
| */ |
| public Expression buildSelectionCriteria(boolean useParameter, boolean usePreviousSelectionCriteria){ |
| Expression criteria = null; |
| if (usePreviousSelectionCriteria){ |
| criteria = getSelectionCriteria(); |
| } |
| |
| if(this.mechanism == null) { |
| Expression builder = new ExpressionBuilder(); |
| // CR3922 |
| if (getSourceToTargetKeyFields().isEmpty()) { |
| throw DescriptorException.noForeignKeysAreSpecified(this); |
| } |
| |
| for (Iterator<DatabaseField> keys = getSourceToTargetKeyFields().keySet().iterator(); keys.hasNext();) { |
| DatabaseField foreignKey = keys.next(); |
| DatabaseField targetKey = getSourceToTargetKeyFields().get(foreignKey); |
| |
| Expression targetKeyExpression = builder.getField(targetKey); |
| Expression sourceKeyExpression = null; |
| |
| if (useParameter){ |
| sourceKeyExpression = builder.getParameter(foreignKey); |
| } else { |
| sourceKeyExpression = builder.getField(foreignKey); |
| } |
| |
| if (usePreviousSelectionCriteria == false) { |
| this.sourceExpressionsToPostInitialize.add(sourceKeyExpression); |
| this.targetExpressionsToPostInitialize.add(targetKeyExpression); |
| } |
| |
| Expression expression = targetKeyExpression.equal(sourceKeyExpression); |
| criteria = expression.and(criteria); |
| } |
| } else { |
| criteria = this.mechanism.buildSelectionCriteria(this, criteria); |
| } |
| return criteria; |
| } |
| |
| /** |
| * INTERNAL: |
| * Builds a shallow original object. Only direct attributes and primary |
| * keys are populated. In this way the minimum original required for |
| * instantiating a working copy clone can be built without placing it in |
| * the shared cache (no concern over cycles). |
| */ |
| @Override |
| public void buildShallowOriginalFromRow(AbstractRecord databaseRow, Object original, JoinedAttributeManager joinManager, ObjectBuildingQuery query, AbstractSession executionSession) { |
| // Now we are only building this original so we can extract the primary |
| // key out of it. If the primary key is stored across a 1-1 a value |
| // holder needs to be built/triggered to get at it. |
| // In this case recursively build the shallow original across the 1-1. |
| // We only need the primary key for that object, and we know |
| // what that primary key is: it is the foreign key in our row. |
| ClassDescriptor descriptor = getReferenceDescriptor(); |
| AbstractRecord targetRow = new DatabaseRecord(); |
| |
| for (Iterator<DatabaseField> keys = getSourceToTargetKeyFields().keySet().iterator(); keys.hasNext();) { |
| DatabaseField foreignKey = keys.next(); |
| DatabaseField targetKey = getSourceToTargetKeyFields().get(foreignKey); |
| |
| targetRow.put(targetKey, databaseRow.get(foreignKey)); |
| } |
| |
| Object targetObject = descriptor.getObjectBuilder().buildNewInstance(); |
| descriptor.getObjectBuilder().buildAttributesIntoShallowObject(targetObject, targetRow, query); |
| targetObject = getIndirectionPolicy().valueFromRow(targetObject); |
| |
| setAttributeValueInObject(original, targetObject); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isOneToOneMapping() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isOwned(){ |
| return this.hasRelationTable() && ! this.isReadOnly; |
| } |
| |
| /** |
| * INTERNAL: |
| * Reads the private owned object. |
| */ |
| @Override |
| protected Object readPrivateOwnedForObject(ObjectLevelModifyQuery modifyQuery) throws DatabaseException { |
| if (modifyQuery.getSession().isUnitOfWork()) { |
| return super.readPrivateOwnedForObject(modifyQuery); |
| } else { |
| if (!shouldVerifyDelete()) { |
| return null; |
| } |
| ReadObjectQuery readQuery = (ReadObjectQuery)getSelectionQuery().clone(); |
| |
| readQuery.setSelectionCriteria(getPrivateOwnedCriteria()); |
| return modifyQuery.getSession().executeQuery(readQuery, modifyQuery.getTranslationRow()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Rehash any map based on fields. |
| * This is used to clone descriptors for aggregates, which hammer field names, |
| * it is probably better not to hammer the field name and this should be refactored. |
| */ |
| @Override |
| public void rehashFieldDependancies(AbstractSession session) { |
| setSourceToTargetKeyFields(Helper.rehashMap(getSourceToTargetKeyFields())); |
| |
| // Go through the fks again and make updates for any translated fields. |
| for (DatabaseField field : getSourceToTargetKeyFields().keySet()) { |
| if (field.isTranslated()) { |
| updateInsertableAndUpdatableFields(field); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether this mapping requires extra queries to update the rows if it is |
| * used as a key in a map. This will typically be true if there are any parts to this mapping |
| * that are not read-only. |
| */ |
| @Override |
| public boolean requiresDataModificationEventsForMapKey() { |
| return true; |
| } |
| |
| /** |
| * Return if this mapping is really for a OneToOne relationship. |
| * This is a backward compatibility issue, in that before the ManyToOneMapping |
| * was created OneToOneMapping was used for both. |
| * false means it may be a OneToOne or a ManyToOne (unknown). |
| */ |
| public boolean isOneToOneRelationship() { |
| return isOneToOneRelationship; |
| } |
| |
| /** |
| * Return if this mapping is mapped using primary key join columns. |
| */ |
| public boolean isOneToOnePrimaryKeyRelationship() { |
| return isOneToOnePrimaryKeyRelationship; |
| } |
| |
| /** |
| * Define if this mapping is really for a OneToOne relationship. |
| * This is a backward compatibility issue, in that before the ManyToOneMapping |
| * was created OneToOneMapping was used for both. |
| */ |
| public void setIsOneToOneRelationship(boolean isOneToOneRelationship) { |
| this.isOneToOneRelationship = isOneToOneRelationship; |
| } |
| |
| /** |
| * Set if this mapping is defined using primary key join columns. |
| */ |
| public void setIsOneToOnePrimaryKeyRelationship(boolean isOneToOnePrimaryKeyRelationship) { |
| this.isOneToOnePrimaryKeyRelationship = isOneToOnePrimaryKeyRelationship; |
| } |
| |
| /** |
| * PUBLIC: |
| * Define the foreign key relationship in the 1-1 mapping. |
| * This method is used for singleton foreign key relationships only, |
| * that is the source object's table has a foreign key field to |
| * the target object's primary key field. |
| * Only the source foreign key field name is specified. |
| * When a foreign key is specified TopLink will automatically populate the value |
| * for that field from the target object when the object is written to the database. |
| * If the foreign key is also mapped through a direct-to-field then the direct-to-field must |
| * be set read-only. |
| */ |
| public void setForeignKeyFieldName(String sourceForeignKeyFieldName) { |
| DatabaseField sourceField = new DatabaseField(sourceForeignKeyFieldName); |
| |
| setIsForeignKeyRelationship(true); |
| getForeignKeyFields().addElement(sourceField); |
| getSourceToTargetKeyFields().put(sourceField, new DatabaseField()); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the foreign key field names associated with the mapping. |
| * These are only the source fields that are writable. |
| */ |
| public void setForeignKeyFieldNames(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())); |
| } |
| |
| setForeignKeyFields(fields); |
| } |
| |
| /** |
| * INTERNAL: |
| * Private owned criteria is used to verify the deletion of the target. |
| * It joins from the source table on the foreign key to the target table, |
| * with a parameterization of the primary key of the source object. |
| */ |
| protected void setPrivateOwnedCriteria(Expression expression) { |
| privateOwnedCriteria = expression; |
| } |
| |
| /** |
| * PUBLIC: |
| * Verify delete is used during delete and update on private 1:1's outside of a unit of work only. |
| * It checks for the previous value of the target object through joining the source and target tables. |
| * By default it is always done, but may be disabled for performance on distributed database reasons. |
| * In the unit of work the previous value is obtained from the backup-clone so it is never used. |
| */ |
| public void setShouldVerifyDelete(boolean shouldVerifyDelete) { |
| this.shouldVerifyDelete = shouldVerifyDelete; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set a collection of the source to target field associations. |
| */ |
| public void setSourceToTargetKeyFieldAssociations(Vector sourceToTargetKeyFieldAssociations) { |
| setSourceToTargetKeyFields(new HashMap(sourceToTargetKeyFieldAssociations.size() + 1)); |
| setTargetToSourceKeyFields(new HashMap(sourceToTargetKeyFieldAssociations.size() + 1)); |
| for (Enumeration associationsEnum = sourceToTargetKeyFieldAssociations.elements(); |
| associationsEnum.hasMoreElements();) { |
| Association association = (Association)associationsEnum.nextElement(); |
| DatabaseField sourceField = new DatabaseField((String)association.getKey()); |
| DatabaseField targetField = new DatabaseField((String)association.getValue()); |
| getSourceToTargetKeyFields().put(sourceField, targetField); |
| getTargetToSourceKeyFields().put(targetField, sourceField); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the source keys to target keys fields association. |
| */ |
| public void setSourceToTargetKeyFields(Map<DatabaseField, DatabaseField> sourceToTargetKeyFields) { |
| this.sourceToTargetKeyFields = sourceToTargetKeyFields; |
| } |
| |
| /** |
| * PUBLIC: |
| * Define the target foreign key relationship in the 1-1 mapping. |
| * This method is used for singleton target foreign key relationships only, |
| * that is the target object's table has a foreign key field to |
| * the source object's primary key field. |
| * The target foreign key field name is specified. |
| * The distinction between a foreign key and target foreign key is that the 1-1 |
| * mapping will not populate the target foreign key value when written (because it is in the target table). |
| * Normally 1-1's are through foreign keys but in bi-directional 1-1's |
| * the back reference will be a target foreign key. |
| */ |
| public void setTargetForeignKeyFieldName(String targetForeignKeyFieldName) { |
| DatabaseField targetField = new DatabaseField(targetForeignKeyFieldName); |
| getTargetToSourceKeyFields().put(targetField, new DatabaseField()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the target keys to source keys fields association. |
| */ |
| public void setTargetToSourceKeyFields(Map<DatabaseField, DatabaseField> targetToSourceKeyFields) { |
| this.targetToSourceKeyFields = targetToSourceKeyFields; |
| } |
| |
| |
| /** |
| * PUBLIC: |
| * Verify delete is used during delete and update outside of a unit of work only. |
| * It checks for the previous value of the target object through joining the source and target tables. |
| */ |
| public boolean shouldVerifyDelete() { |
| return shouldVerifyDelete; |
| } |
| |
| /** |
| * INTERNAL: |
| * By default returns true. Will also return true if: |
| * 1 - WriteType is INSERT and the field is insertable. |
| * 2 - WriteType is UPDATE and the field is updatable. |
| */ |
| protected boolean shouldWriteField(DatabaseField field, WriteType writeType) { |
| if (writeType.equals(WriteType.INSERT)) { |
| return insertableFields.contains(field); |
| } else if (writeType.equals(WriteType.UPDATE)) { |
| return updatableFields.contains(field); |
| } else { |
| return true; // UNDEFINED, default is to write. |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Return true if this mapping supports cascaded version optimistic locking. |
| */ |
| @Override |
| public boolean isCascadedLockingSupported() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if this mapping support joining. |
| */ |
| @Override |
| public boolean isJoiningSupported() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Called when iterating through descriptors to handle iteration on this mapping when it is used as a MapKey |
| */ |
| @Override |
| public void iterateOnMapKey(DescriptorIterator iterator, Object element){ |
| this.getIndirectionPolicy().iterateOnAttributeValue(iterator, element); |
| } |
| |
| /** |
| * INTERNAL: |
| * Allow the key mapping to unwrap the object. |
| */ |
| @Override |
| public Object unwrapKey(Object key, AbstractSession session){ |
| return getDescriptor().getObjectBuilder().unwrapObject(key, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Add the field to the updatable and/or insertable list. Remove any |
| * previous field under the same name, otherwise shouldn't matter if we |
| * leave an old name (before translation) in the list as it should 'never' |
| * be used anyway. |
| */ |
| protected void updateInsertableAndUpdatableFields(DatabaseField field) { |
| insertableFields.remove(field); |
| updatableFields.remove(field); |
| |
| if (field.isInsertable()) { |
| insertableFields.add(field); |
| } |
| |
| if (field.isUpdatable()) { |
| updatableFields.add(field); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Allow the key mapping to wrap the object. |
| */ |
| @Override |
| public Object wrapKey(Object key, AbstractSession session){ |
| return getDescriptor().getObjectBuilder().wrapObject(key, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * A subclass should implement this method if it wants different behavior. |
| * Write the foreign key values from the attribute to the row. |
| */ |
| @Override |
| public void writeFromAttributeIntoRow(Object attribute, AbstractRecord row, AbstractSession session) |
| { |
| for (Enumeration<DatabaseField> fieldsEnum = getForeignKeyFields().elements(); fieldsEnum.hasMoreElements();) { |
| DatabaseField sourceKey = fieldsEnum.nextElement(); |
| DatabaseField targetKey = getSourceToTargetKeyFields().get(sourceKey); |
| Object referenceValue = null; |
| // If privately owned part is null then method cannot be invoked. |
| if (attribute != null) { |
| referenceValue = getReferenceDescriptor().getObjectBuilder().extractValueFromObjectForField(attribute, targetKey, session); |
| } |
| row.add(sourceKey, referenceValue); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Get a value from the object and set that in the respective field of the row. |
| */ |
| @Override |
| public Object valueFromObject(Object object, DatabaseField field, AbstractSession session) { |
| // First check if the value can be obtained from the value holder's row. |
| Object attributeValue = getAttributeValueFromObject(object); |
| AbstractRecord referenceRow = this.indirectionPolicy.extractReferenceRow(attributeValue); |
| if (referenceRow != null) { |
| Object value = referenceRow.get(field); |
| Class<?> type = getFieldClassification(field); |
| if ((value == null) || (value.getClass() != type)) { |
| // Must ensure the classification to get a cache hit. |
| try { |
| value = session.getDatasourcePlatform().convertObject(value, type); |
| } catch (ConversionException exception) { |
| throw ConversionException.couldNotBeConverted(this, getDescriptor(), exception); |
| } |
| } |
| return value; |
| } |
| |
| Object referenceObject = getRealAttributeValueFromAttribute(attributeValue, object, session); |
| if (referenceObject == null) { |
| return null; |
| } |
| DatabaseField targetField; |
| if(this.mechanism == null) { |
| targetField = this.sourceToTargetKeyFields.get(field); |
| } else { |
| targetField = this.mechanism.targetKeyFields.get(this.mechanism.sourceKeyFields.indexOf(field)); |
| } |
| |
| return this.referenceDescriptor.getObjectBuilder().extractValueFromObjectForField(referenceObject, targetField, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the value of the field from the row or a value holder on the query to obtain the object. |
| * Check for batch + aggregation reading. |
| */ |
| @Override |
| protected Object valueFromRowInternalWithJoin(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, CacheKey parentCacheKey, AbstractSession executionSession, boolean isTargetProtected) throws DatabaseException { |
| // PERF: Direct variable access. |
| Object referenceObject; |
| // CR #... 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. |
| AbstractRecord targetRow = trimRowForJoin(row, joinManager, executionSession); |
| // PERF: Only check for null row if an outer-join was used. |
| if (((joinManager != null) && joinManager.hasOuterJoinedAttributeQuery()) && !sourceQuery.hasPartialAttributeExpressions()) { |
| Object key = this.referenceDescriptor.getObjectBuilder().extractPrimaryKeyFromRow(targetRow, executionSession); |
| if (key == null) { |
| return this.indirectionPolicy.nullValueFromRow(); |
| } |
| } |
| // A nested query must be built to pass to the descriptor that looks like the real query execution would, |
| // these should be cached on the query during prepare. |
| ObjectLevelReadQuery nestedQuery = prepareNestedJoinQueryClone(row, null, joinManager, sourceQuery, executionSession); |
| nestedQuery.setTranslationRow(targetRow); |
| nestedQuery.setRequiresDeferredLocks(sourceQuery.requiresDeferredLocks()); |
| nestedQuery.setPrefetchedCacheKeys(sourceQuery.getPrefetchedCacheKeys()); |
| nestedQuery.setShouldRefreshIdentityMapResult(sourceQuery.shouldRefreshIdentityMapResult()); |
| referenceObject = this.referenceDescriptor.getObjectBuilder().buildObject(nestedQuery, targetRow); |
| |
| // For bug 3641713 buildObject doesn't wrap if called on a UnitOfWork for performance reasons, |
| // must wrap here as this is the last time we can look at the query and tell whether to wrap or not. |
| if (nestedQuery.shouldUseWrapperPolicy() && executionSession.isUnitOfWork()) { |
| referenceObject = this.referenceDescriptor.getObjectBuilder().wrapObject(referenceObject, executionSession); |
| } |
| return this.indirectionPolicy.valueFromRow(referenceObject); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the value of the field from the row or a value holder on the query to obtain the object. |
| * Check for batch + aggregation reading. |
| */ |
| @Override |
| protected Object valueFromRowInternal(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, AbstractSession executionSession, boolean shouldUseSopObject) throws DatabaseException { |
| // If any field in the foreign key is null then it means there are no referenced objects |
| // Skip for partial objects as fk may not be present. |
| if (!shouldUseSopObject) { |
| int size = this.fields.size(); |
| for (int index = 0; index < size; index++) { |
| DatabaseField field = this.fields.get(index); |
| if (row.get(field) == null) { |
| return this.indirectionPolicy.nullValueFromRow(); |
| } |
| } |
| } |
| |
| // Call the default which executes the selection query, |
| // or wraps the query with a value holder. |
| return super.valueFromRowInternal(row, joinManager, sourceQuery, executionSession, shouldUseSopObject); |
| } |
| |
| /** |
| * INTERNAL: |
| * Get a value from the object and set that in the respective field of the row. |
| */ |
| @Override |
| public void writeFromObjectIntoRow(Object object, AbstractRecord databaseRow, AbstractSession session, WriteType writeType) { |
| if (this.isReadOnly || (!this.isForeignKeyRelationship)) { |
| return; |
| } |
| writeFromObjectIntoRowInternal(object, databaseRow, session, null, writeType); |
| } |
| |
| /** |
| * INTERNAL: |
| * Get a value from the object and set that in the respective field of the row. |
| * The fields and the values added to the row depend on ShallowMode mode: |
| * null - all fields with their values from object; |
| * Insert - nullable fields added with value null, non nullable fields added with their values from object; |
| * UpdateAfterInsert - nullable fields added with with their non-null values from object, non nullable fields (and nullable with null values) are ignored; |
| * UpdateBeforeDelete - the same fields as for UpdateAfterShallowInsert - but all values are nulls. |
| */ |
| protected void writeFromObjectIntoRowInternal(Object object, AbstractRecord databaseRow, AbstractSession session, ShallowMode mode, WriteType writeType) { |
| List<DatabaseField> foreignKeyFields = getForeignKeyFields(); |
| if (mode != null) { |
| List<DatabaseField> nonNullableFields = null; |
| |
| for (DatabaseField field : foreignKeyFields) { |
| if (field.isNullable()) { |
| if (mode == ShallowMode.Insert && shouldWriteField(field, writeType)) { |
| // add a nullable field with a null value |
| databaseRow.add(field, null); |
| } |
| } else { |
| if (nonNullableFields == null) { |
| nonNullableFields = new ArrayList<>(); |
| } |
| |
| nonNullableFields.add(field); |
| } |
| } |
| |
| if (nonNullableFields == null) { |
| // all foreignKeyFields are nullable |
| if (mode == ShallowMode.Insert) { |
| // nothing else to do |
| return; |
| } |
| // UpdateAfterInsert or UpdateBeforeDelete: all nullable foreignKeyFields will be processed |
| } else { |
| if (mode == ShallowMode.Insert) { |
| // all non nullable foreignKeyFields will be processed |
| foreignKeyFields = nonNullableFields; |
| } else { |
| // UpdateAfterInsert or UpdateBeforeDelete |
| if (foreignKeyFields.size() == nonNullableFields.size()) { |
| // all fields are non nullable - nothing else to do |
| return; |
| } else { |
| // all nullable foreignKeyFields will be processed |
| foreignKeyFields = new ArrayList<>(foreignKeyFields); |
| foreignKeyFields.removeAll(nonNullableFields); |
| } |
| } |
| } |
| } |
| |
| Object attributeValue = getAttributeValueFromObject(object); |
| // If the value holder has the row, avoid instantiation and just use it. |
| AbstractRecord referenceRow = this.indirectionPolicy.extractReferenceRow(attributeValue); |
| if (referenceRow == null) { |
| // Extract from object. |
| Object referenceObject = getRealAttributeValueFromAttribute(attributeValue, object, session); |
| |
| for (DatabaseField sourceKey : foreignKeyFields) { |
| Object referenceValue = null; |
| |
| // If privately owned part is null then method cannot be invoked. |
| if (referenceObject != null) { |
| DatabaseField targetKey = this.sourceToTargetKeyFields.get(sourceKey); |
| referenceValue = this.referenceDescriptor.getObjectBuilder().extractValueFromObjectForField(referenceObject, targetKey, session); |
| } |
| |
| if (mode == null) { |
| // EL Bug 319759 - if a field is null, then the update call cache should not be used |
| if (referenceValue == null) { |
| databaseRow.setNullValueInFields(true); |
| } |
| } else { |
| if (referenceValue == null) { |
| if (mode != ShallowMode.Insert) { |
| // both UpdateAfterInsert and UpdateBeforeDelete ignore null values |
| continue; |
| } |
| } else { |
| if (mode == ShallowMode.UpdateBeforeDelete) { |
| // UpdateBeforeDelete adds nulls instead of non nulls |
| referenceValue = null; |
| } |
| } |
| } |
| |
| // Check updatable and insertable based on the write type. |
| if (shouldWriteField(sourceKey, writeType)) { |
| databaseRow.add(sourceKey, referenceValue); |
| } |
| } |
| } else { |
| for (DatabaseField sourceKey : foreignKeyFields) { |
| Object referenceValue = referenceRow.get(sourceKey); |
| |
| if (mode == null) { |
| // EL Bug 319759 - if a field is null, then the update call cache should not be used |
| if (referenceValue == null) { |
| databaseRow.setNullValueInFields(true); |
| } |
| } else { |
| if (referenceValue == null) { |
| if (mode != ShallowMode.Insert) { |
| // both UpdateAfterInsert and UpdateBeforeDelete ignore null values |
| continue; |
| } |
| } else { |
| if (mode == ShallowMode.UpdateBeforeDelete) { |
| // UpdateBeforeDelete adds nulls instead of non nulls |
| referenceValue = null; |
| } |
| } |
| } |
| |
| // Check updatable and insertable based on the write type. |
| if (shouldWriteField(sourceKey, writeType)) { |
| databaseRow.add(sourceKey, referenceValue); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This row is built for shallow insert which happens in case of bidirectional inserts. |
| * The foreign keys must be set to null to avoid constraints. |
| */ |
| @Override |
| public void writeFromObjectIntoRowForShallowInsert(Object object, AbstractRecord databaseRow, AbstractSession session) { |
| if (this.isReadOnly || (!this.isForeignKeyRelationship)) { |
| return; |
| } |
| writeFromObjectIntoRowInternal(object, databaseRow, session, ShallowMode.Insert, WriteType.INSERT); |
| } |
| |
| /** |
| * INTERNAL: |
| * This row is built for update after shallow insert which happens in case of bidirectional inserts. |
| * It contains the foreign keys with non null values that were set to null for shallow insert. |
| */ |
| @Override |
| public void writeFromObjectIntoRowForUpdateAfterShallowInsert(Object object, AbstractRecord databaseRow, AbstractSession session, DatabaseTable table) { |
| if (this.isReadOnly || (!this.isForeignKeyRelationship) || !getFields().get(0).getTable().equals(table) || isPrimaryKeyMapping()) { |
| return; |
| } |
| writeFromObjectIntoRowInternal(object, databaseRow, session, ShallowMode.UpdateAfterInsert, WriteType.UNDEFINED); |
| } |
| |
| /** |
| * INTERNAL: |
| * This row is built for update before shallow delete which happens in case of bidirectional inserts. |
| * It contains the same fields as the row built by writeFromObjectIntoRowForUpdateAfterShallowInsert, but all the values are null. |
| */ |
| @Override |
| public void writeFromObjectIntoRowForUpdateBeforeShallowDelete(Object object, AbstractRecord databaseRow, AbstractSession session, DatabaseTable table) { |
| if (this.isReadOnly || (!this.isForeignKeyRelationship) || !getFields().get(0).getTable().equals(table) || isPrimaryKeyMapping()) { |
| return; |
| } |
| writeFromObjectIntoRowInternal(object, databaseRow, session, ShallowMode.UpdateBeforeDelete, WriteType.UNDEFINED); |
| } |
| |
| /** |
| * INTERNAL: |
| * Get a value from the object and set that in the respective field of the row. |
| * Validation preventing primary key updates is implemented here. |
| */ |
| @Override |
| public void writeFromObjectIntoRowWithChangeRecord(ChangeRecord changeRecord, AbstractRecord databaseRow, AbstractSession session, WriteType writeType) { |
| if ((!this.isReadOnly) && this.isPrimaryKeyMapping && (!changeRecord.getOwner().isNew())) { |
| throw ValidationException.primaryKeyUpdateDisallowed(changeRecord.getOwner().getClassName(), changeRecord.getAttribute()); |
| } |
| |
| // The object must be used here as the foreign key may include more than just the |
| // primary key of the referenced object and the changeSet may not have the required information. |
| Object object = ((ObjectChangeSet)changeRecord.getOwner()).getUnitOfWorkClone(); |
| writeFromObjectIntoRow(object, databaseRow, session, writeType); |
| } |
| |
| /** |
| * INTERNAL: |
| * This row is built for shallow insert which happens in case of bidirectional inserts. |
| * The foreign keys must be set to null to avoid constraints. |
| */ |
| @Override |
| public void writeFromObjectIntoRowForShallowInsertWithChangeRecord(ChangeRecord ChangeRecord, AbstractRecord databaseRow, AbstractSession session) { |
| if (isReadOnly() || (!isForeignKeyRelationship())) { |
| return; |
| } |
| |
| for (Enumeration<DatabaseField> fieldsEnum = getForeignKeyFields().elements(); |
| fieldsEnum.hasMoreElements();) { |
| DatabaseField sourceKey = fieldsEnum.nextElement(); |
| databaseRow.add(sourceKey, null); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Write fields needed for insert into the template for with null values. |
| */ |
| @Override |
| public void writeInsertFieldsIntoRow(AbstractRecord databaseRow, AbstractSession session) { |
| if (isReadOnly() || (!isForeignKeyRelationship())) { |
| return; |
| } |
| |
| for (Enumeration<DatabaseField> fieldsEnum = getForeignKeyFields().elements(); fieldsEnum.hasMoreElements();) { |
| DatabaseField sourceKey = fieldsEnum.nextElement(); |
| |
| if (shouldWriteField(sourceKey, WriteType.INSERT)) { |
| databaseRow.add(sourceKey, null); |
| } |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Indicates whether the mapping has RelationTableMechanism. |
| */ |
| @Override |
| public boolean hasRelationTableMechanism() { |
| return this.mechanism != null; |
| } |
| |
| /** |
| * PUBLIC: |
| * Indicates whether the mapping has RelationTable. |
| */ |
| public boolean hasRelationTable() { |
| return this.mechanism != null && this.mechanism.hasRelationTable(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Returns RelationTableMechanism that may be owned by the mapping, |
| * that allows to configure the mapping to use relation table (just like ManyToManyMapping). |
| * By default its null, should be created and set into the mapping before use. |
| */ |
| public RelationTableMechanism getRelationTableMechanism() { |
| return this.mechanism; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the relational table. |
| * This is the join table that store both the source and target primary keys. |
| */ |
| public void setRelationTable(DatabaseTable relationTable) { |
| this.mechanism.setRelationTable(relationTable); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set RelationTableMechanism into the mapping, |
| * that allows to configure the mapping to use relation table (just like ManyToManyMapping). |
| */ |
| public void setRelationTableMechanism(RelationTableMechanism mechanism) { |
| this.mechanism = mechanism; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return RelationTable. |
| */ |
| public DatabaseTable getRelationTable() { |
| if(this.mechanism != null) { |
| return this.mechanism.getRelationTable(); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Delete privately owned parts |
| */ |
| @Override |
| public void preDelete(DeleteObjectQuery query) throws DatabaseException, OptimisticLockException { |
| if ((this.mechanism != null) && !this.isReadOnly && !this.isCascadeOnDeleteSetOnDatabase) { |
| AbstractRecord sourceRow = this.mechanism.buildRelationTableSourceRow(query.getObject(), query.getSession(), this); |
| query.getSession().executeQuery(this.mechanism.deleteQuery, sourceRow); |
| } |
| super.preDelete(query); |
| } |
| |
| /** |
| * INTERNAL: |
| * Insert into relation table. This follows following steps. |
| * <p>- Extract primary key and its value from the source object. |
| * <p>- Extract target key and its value from the target object. |
| * <p>- Construct a insert statement with above fields and values for relation table. |
| * <p>- execute the statement. |
| */ |
| @Override |
| public void postInsert(WriteObjectQuery query) throws DatabaseException { |
| super.postInsert(query); |
| if(this.mechanism != null && !isReadOnly()) { |
| Object targetObject = getRealAttributeValueFromObject(query.getObject(), query.getSession()); |
| if (targetObject == null) { |
| return; |
| } |
| |
| // Batch data modification in the uow |
| 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] = setObject; |
| event[1] = this.mechanism.buildRelationTableSourceRow(query.getObject(), query.getSession(), this); |
| // targetObject may not have pk yet - wait to extract targetRow until the event is processed |
| event[2] = targetObject; |
| query.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } else { |
| AbstractRecord sourceAndTargetRow = this.mechanism.buildRelationTableSourceAndTargetRow(query.getObject(), targetObject, query.getSession(), this); |
| query.getSession().executeQuery(this.mechanism.insertQuery, sourceAndTargetRow); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Update the relation table with the entries related to this mapping. |
| * Delete entries removed, insert entries added. |
| * If private also insert/delete/update target objects. |
| */ |
| @Override |
| public void postUpdate(WriteObjectQuery query) throws DatabaseException { |
| if(this.mechanism == null) { |
| super.postUpdate(query); |
| } else { |
| // If object is not instantiated then it's not changed. |
| if (!isAttributeValueInstantiated(query.getObject())) { |
| return; |
| } |
| |
| AbstractRecord sourceRow = null; |
| if(!isReadOnly()) { |
| sourceRow = this.mechanism.buildRelationTableSourceRow(query.getObject(), query.getSession(), this); |
| query.getSession().executeQuery(this.mechanism.deleteQuery, sourceRow); |
| } |
| |
| super.postUpdate(query); |
| |
| if(sourceRow != null) { |
| Object targetObject = getRealAttributeValueFromObject(query.getObject(), query.getSession()); |
| if (targetObject == null) { |
| return; |
| } |
| // Batch data modification in the uow |
| 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] = setObject; |
| event[1] = sourceRow; |
| // targetObject may not have pk yet - wait to extract targetRow until the event is processed |
| event[2] = targetObject; |
| query.getSession().getCommitManager().addDataModificationEvent(this, event); |
| } else { |
| AbstractRecord sourceAndTargetRow = this.mechanism.addRelationTableTargetRow(targetObject, query.getExecutionSession(), sourceRow, this); |
| query.getSession().executeQuery(this.mechanism.insertQuery, sourceAndTargetRow); |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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] == setObject) { |
| AbstractRecord sourceAndTargetRow = this.mechanism.addRelationTableTargetRow(event[2], session, (AbstractRecord)event[1], this); |
| session.executeQuery(this.mechanism.insertQuery, sourceAndTargetRow); |
| } else { |
| throw DescriptorException.invalidDataModificationEventCode(event[0], this); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return all the fields populated by this mapping, these are foreign keys only. |
| */ |
| @Override |
| protected Vector<DatabaseField> collectFields() { |
| if(this.mechanism != null) { |
| return new Vector(0); |
| } else { |
| return super.collectFields(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Order by foreign key fields if a foreign key mapping (avoids joins). |
| */ |
| @Override |
| public List<Expression> getOrderByNormalizedExpressions(Expression base) { |
| if (this.foreignKeyFields.size() > 0) { |
| List<Expression> orderBys = new ArrayList(this.foreignKeyFields.size()); |
| for (DatabaseField field : this.foreignKeyFields) { |
| orderBys.add(((QueryKeyExpression)base).getBaseExpression().getField(field)); |
| } |
| return orderBys; |
| } |
| return super.getOrderByNormalizedExpressions(base); |
| } |
| } |