| /* |
| * Copyright (c) 1998, 2019 Oracle and/or its affiliates. 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 |
| package org.eclipse.persistence.mappings; |
| |
| import java.security.AccessController; |
| import java.security.PrivilegedActionException; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Vector; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| 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.indirection.ValueHolder; |
| 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.CacheKey; |
| import org.eclipse.persistence.internal.queries.JoinedAttributeManager; |
| import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; |
| import org.eclipse.persistence.internal.security.PrivilegedClassForName; |
| 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.ObjectChangeSet; |
| import org.eclipse.persistence.internal.sessions.ObjectReferenceChangeRecord; |
| import org.eclipse.persistence.mappings.querykeys.DirectQueryKey; |
| import org.eclipse.persistence.mappings.querykeys.QueryKey; |
| import org.eclipse.persistence.queries.ObjectBuildingQuery; |
| import org.eclipse.persistence.queries.ObjectLevelModifyQuery; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.queries.ReadObjectQuery; |
| |
| /** |
| * <p><b>Purpose</b>: Variable one to one mappings are used to represent a pointer references |
| * between a java object and an implementer of an interface. This mapping 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 a foreign key and a type code. |
| * |
| * @author Sati |
| * @since TOPLink/Java 2.0 |
| */ |
| public class VariableOneToOneMapping extends ObjectReferenceMapping implements RelationalMapping { |
| protected DatabaseField typeField; |
| protected Map sourceToTargetQueryKeyNames; |
| protected Map typeIndicatorTranslation; |
| |
| /** parallel table typeIndicatorTranslation used prior to initialization to avoid type indicators on Mapping Workbench */ |
| protected Map typeIndicatorNameTranslation; |
| |
| /** |
| * PUBLIC: |
| * Default constructor. |
| */ |
| public VariableOneToOneMapping() { |
| this.selectionQuery = new ReadObjectQuery(); |
| this.sourceToTargetQueryKeyNames = new HashMap(2); |
| this.typeIndicatorTranslation = new HashMap(5); |
| this.typeIndicatorNameTranslation = new HashMap(5); |
| this.foreignKeyFields = NonSynchronizedVector.newInstance(1); |
| |
| //right now only ForeignKeyRelationships are supported |
| this.isForeignKeyRelationship = false; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isRelationalMapping() { |
| return true; |
| } |
| |
| /** |
| * PUBLIC: |
| * Add a type indicator conversion to this mapping. |
| */ |
| public void addClassIndicator(Class implementer, Object typeIndicator) { |
| if (typeIndicator == null) { |
| typeIndicator = Helper.NULL_VALUE; |
| } |
| |
| getTypeIndicatorTranslation().put(implementer, typeIndicator); |
| getTypeIndicatorTranslation().put(typeIndicator, implementer); |
| } |
| |
| /** |
| * INTERNAL: |
| * Add indicators by classname. For use by the Mapping Workbench to avoid classpath dependencies |
| */ |
| public void addClassNameIndicator(String className, Object typeIndicator) { |
| if (typeIndicator == null) { |
| typeIndicator = Helper.NULL_VALUE; |
| } |
| getTypeIndicatorNameTranslation().put(className, typeIndicator); |
| } |
| |
| /** |
| * PUBLIC: |
| * A foreign key from the source table and abstract query key from the interface descriptor are added to the |
| * mapping. This method is used if there are multiple foreign keys. |
| */ |
| public void addForeignQueryKeyName(DatabaseField sourceForeignKeyField, String targetQueryKeyName) { |
| getSourceToTargetQueryKeyNames().put(sourceForeignKeyField, targetQueryKeyName); |
| getForeignKeyFields().addElement(sourceForeignKeyField); |
| this.setIsForeignKeyRelationship(true); |
| } |
| |
| /** |
| * PUBLIC: |
| * A foreign key from the source table and abstract query key from the interface descriptor are added to the |
| * mapping. This method is used if there are multiple foreign keys. |
| */ |
| public void addForeignQueryKeyName(String sourceForeignKeyFieldName, String targetQueryKeyName) { |
| addForeignQueryKeyName(new DatabaseField(sourceForeignKeyFieldName), targetQueryKeyName); |
| } |
| |
| /** |
| * PUBLIC: |
| * Define the target foreign key relationship in the Variable 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 query name and the source primary key field name |
| * must be specified. |
| * The distinction between a foreign key and target foreign key is that the variable 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 addTargetForeignQueryKeyName(String targetForeignQueryKeyName, String sourcePrimaryKeyFieldName) { |
| DatabaseField sourceField = new DatabaseField(sourcePrimaryKeyFieldName); |
| |
| getSourceToTargetQueryKeyNames().put(sourceField, targetForeignQueryKeyName); |
| } |
| |
| /** |
| * INTERNAL: |
| * Possible for future development, not currently supported. |
| * |
| * Retrieve the value through using batch reading. |
| * This executes a single query to read the target for all of the objects and stores the |
| * result of the batch query in the original query to allow the other objects to share the results. |
| */ |
| @Override |
| protected Object batchedValueFromRow(AbstractRecord row, ObjectLevelReadQuery query, CacheKey parentCacheKey) { |
| throw QueryException.batchReadingNotSupported(this, query); |
| } |
| |
| /** |
| * INTERNAL: |
| * This methods clones all the fields and ensures that each collection refers to |
| * the same clones. |
| */ |
| @Override |
| public Object clone() { |
| VariableOneToOneMapping clone = (VariableOneToOneMapping)super.clone(); |
| Map setOfKeys = new HashMap(getSourceToTargetQueryKeyNames().size()); |
| Map sourceToTarget = new HashMap(getSourceToTargetQueryKeyNames().size()); |
| Vector foreignKeys = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(getForeignKeyFields().size()); |
| |
| if (getTypeField() != null) { |
| clone.setTypeField(this.getTypeField().clone()); |
| } |
| |
| for (Iterator enumtr = getSourceToTargetQueryKeyNames().keySet().iterator(); enumtr.hasNext();) { |
| // Clone the SourceKeyFields |
| DatabaseField field = (DatabaseField)enumtr.next(); |
| DatabaseField clonedField = field.clone(); |
| setOfKeys.put(field, clonedField); |
| // on the next line I'm cloning the query key names |
| sourceToTarget.put(clonedField, getSourceToTargetQueryKeyNames().get(field)); |
| } |
| |
| for (Enumeration enumtr = getForeignKeyFields().elements(); enumtr.hasMoreElements();) { |
| DatabaseField field = (DatabaseField)enumtr.nextElement(); |
| foreignKeys.addElement(setOfKeys.get(field)); |
| } |
| clone.setSourceToTargetQueryKeyFields(sourceToTarget); |
| clone.setForeignKeyFields(foreignKeys); |
| clone.setTypeIndicatorTranslation(new HashMap(this.getTypeIndicatorTranslation())); |
| return clone; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return all the fields populated by this mapping. |
| */ |
| @Override |
| protected Vector collectFields() { |
| DatabaseField type = getTypeField(); |
| |
| //Get a shallow copy of the Vector |
| if (type != null) { |
| Vector sourceFields = (Vector)getForeignKeyFields().clone(); |
| sourceFields.addElement(type); |
| return sourceFields; |
| } else { |
| return getForeignKeyFields(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the references of the two objects are the same, not the objects themselves. |
| * Used for independent relationships. |
| * This is used for testing and validation purposes. |
| * |
| * Must get separate fields for the objects because we may be adding a different class to the |
| * attribute because of the interface |
| */ |
| @Override |
| protected boolean compareObjectsWithoutPrivateOwned(Object firstObject, Object secondObject, AbstractSession session) { |
| Object firstPrivateObject = getRealAttributeValueFromObject(firstObject, session); |
| Object secondPrivateObject = getRealAttributeValueFromObject(secondObject, session); |
| |
| if ((firstPrivateObject == null) && (secondPrivateObject == null)) { |
| return true; |
| } |
| |
| if ((firstPrivateObject == null) || (secondPrivateObject == null)) { |
| return false; |
| } |
| if (firstPrivateObject.getClass() != secondPrivateObject.getClass()) { |
| return false; |
| } |
| Iterator targetKeys = getSourceToTargetQueryKeyNames().values().iterator(); |
| ClassDescriptor descriptor = session.getDescriptor(firstPrivateObject.getClass()); |
| ClassDescriptor descriptor2 = session.getDescriptor(secondPrivateObject.getClass()); |
| |
| while (targetKeys.hasNext()) { |
| String queryKey = (String)targetKeys.next(); |
| DatabaseField field = descriptor.getObjectBuilder().getFieldForQueryKeyName(queryKey); |
| Object firstObjectField = descriptor.getObjectBuilder().extractValueFromObjectForField(firstPrivateObject, field, session); |
| DatabaseField field2 = descriptor2.getObjectBuilder().getFieldForQueryKeyName(queryKey); |
| Object secondObjectField = descriptor2.getObjectBuilder().extractValueFromObjectForField(secondPrivateObject, field2, session); |
| |
| if (!((firstObjectField == null) && (secondObjectField == null))) { |
| if ((firstObjectField == null) || (secondObjectField == null)) { |
| return false; |
| } |
| if (!firstObjectField.equals(secondObjectField)) { |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the class indicator associations for XML. |
| * List of class-name/value associations. |
| */ |
| public Vector getClassIndicatorAssociations() { |
| Vector associations = new Vector(); |
| Iterator classesEnum = getTypeIndicatorNameTranslation().keySet().iterator(); |
| Iterator valuesEnum = getTypeIndicatorNameTranslation().values().iterator(); |
| while (classesEnum.hasNext()) { |
| Object className = classesEnum.next(); |
| |
| // If the project was built in runtime is a class, MW is a string. |
| if (className instanceof Class) { |
| className = ((Class)className).getName(); |
| } |
| Object value = valuesEnum.next(); |
| associations.addElement(new TypedAssociation(className, value)); |
| } |
| |
| return associations; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return a descriptor for the target of this mapping |
| * For normal ObjectReferenceMappings, we return the reference descriptor. For |
| * a VariableOneToOneMapping, the reference descriptor is often a descriptor for an |
| * interface and does not contain adequate information. As a result, we look up |
| * the descriptor for the specific class we are looking for |
| * Bug 2612571 |
| */ |
| @Override |
| public ClassDescriptor getDescriptorForTarget(Object targetObject, AbstractSession session) { |
| return session.getDescriptor(targetObject); |
| } |
| |
| /** |
| * 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) { |
| if ((getTypeField() != null) && (fieldToClassify.equals(getTypeField()))) { |
| return getTypeField().getType(); |
| } |
| |
| String queryKey = (String)getSourceToTargetQueryKeyNames().get(fieldToClassify); |
| if (queryKey == null) { |
| return null; |
| } |
| // Search any of the implementor descriptors for a mapping for the query-key. |
| Iterator iterator = getReferenceDescriptor().getInterfacePolicy().getChildDescriptors().iterator(); |
| if (iterator.hasNext()) { |
| ClassDescriptor firstChild = (ClassDescriptor)iterator.next(); |
| DatabaseMapping mapping = firstChild.getObjectBuilder().getMappingForAttributeName(queryKey); |
| if ((mapping != null) && (mapping.isDirectToFieldMapping())) { |
| return mapping.getAttributeClassification(); |
| } |
| QueryKey targetQueryKey = firstChild.getQueryKeyNamed(queryKey); |
| if ((targetQueryKey != null) && (targetQueryKey.isDirectQueryKey())) { |
| return firstChild.getObjectBuilder().getFieldClassification(((DirectQueryKey)targetQueryKey).getField()); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * 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 fieldsEnum = getForeignKeyFields().elements(); |
| fieldsEnum.hasMoreElements();) { |
| fieldNames.addElement(((DatabaseField)fieldsEnum.nextElement()).getQualifiedName()); |
| } |
| |
| return fieldNames; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the implementor for a specified type |
| */ |
| protected Object getImplementorForType(Object type, AbstractSession session) { |
| if (type == null) { |
| return getTypeIndicatorTranslation().get(Helper.NULL_VALUE); |
| } |
| |
| // Must ensure the type is the same, i.e. Integer != BigDecimal. |
| try { |
| type = session.getDatasourcePlatform().convertObject(type, getTypeField().getType()); |
| } catch (ConversionException e) { |
| throw ConversionException.couldNotBeConverted(this, getDescriptor(), e); |
| } |
| |
| return getTypeIndicatorTranslation().get(type); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a collection of the field to query key associations. |
| */ |
| public Vector getSourceToTargetQueryKeyFieldAssociations() { |
| Vector associations = new Vector(getSourceToTargetQueryKeyNames().size()); |
| Iterator sourceFieldEnum = getSourceToTargetQueryKeyNames().keySet().iterator(); |
| Iterator targetQueryKeyEnum = getSourceToTargetQueryKeyNames().values().iterator(); |
| while (sourceFieldEnum.hasNext()) { |
| Object fieldValue = ((DatabaseField)sourceFieldEnum.next()).getQualifiedName(); |
| Object attributeValue = targetQueryKeyEnum.next(); |
| associations.addElement(new Association(fieldValue, attributeValue)); |
| } |
| |
| return associations; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the source keys to target keys fields association. |
| */ |
| public Map getSourceToTargetQueryKeyNames() { |
| return sourceToTargetQueryKeyNames; |
| } |
| |
| public DatabaseField getTypeField() { |
| return typeField; |
| } |
| |
| /** |
| * PUBLIC: |
| * This method returns the name of the typeField of the mapping. |
| * The type field is used to store the type of object the relationship is referencing. |
| */ |
| public String getTypeFieldName() { |
| if (getTypeField() == null) { |
| return null; |
| } |
| return getTypeField().getQualifiedName(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the type for a specified implementor |
| */ |
| protected Object getTypeForImplementor(Class implementor) { |
| Object type = getTypeIndicatorTranslation().get(implementor); |
| if (type == Helper.NULL_VALUE) { |
| type = null; |
| } |
| |
| return type; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the type indicators. |
| */ |
| public Map getTypeIndicatorTranslation() { |
| return typeIndicatorTranslation; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the typeIndicatorName translation |
| * Used by the Mapping Workbench to avoid classpath dependencies |
| */ |
| public Map getTypeIndicatorNameTranslation() { |
| if (typeIndicatorNameTranslation.isEmpty() && !typeIndicatorTranslation.isEmpty()) { |
| Iterator keysEnum = typeIndicatorTranslation.keySet().iterator(); |
| Iterator valuesEnum = typeIndicatorTranslation.values().iterator(); |
| while (keysEnum.hasNext()) { |
| Object key = keysEnum.next(); |
| Object value = valuesEnum.next(); |
| if (key instanceof Class) { |
| String className = ((Class)key).getName(); |
| typeIndicatorNameTranslation.put(className, value); |
| } |
| } |
| } |
| |
| return typeIndicatorNameTranslation; |
| } |
| |
| /** |
| * INTERNAL: |
| * Convert all the class-name-based settings in this mapping to actual class-based |
| * settings. This method is used when converting a project that has been built |
| * with class names to a project with classes. |
| */ |
| @Override |
| public void convertClassNamesToClasses(ClassLoader classLoader){ |
| super.convertClassNamesToClasses(classLoader); |
| Iterator iterator = getTypeIndicatorNameTranslation().entrySet().iterator(); |
| this.typeIndicatorTranslation = new HashMap(); |
| while (iterator.hasNext()) { |
| Map.Entry entry = (Map.Entry)iterator.next(); |
| String referenceClassName = (String)entry.getKey(); |
| Object indicator = entry.getValue(); |
| Class referenceClass = null; |
| try{ |
| if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) { |
| try { |
| referenceClass = AccessController.doPrivileged(new PrivilegedClassForName(referenceClassName, true, classLoader)); |
| } catch (PrivilegedActionException exception) { |
| throw ValidationException.classNotFoundWhileConvertingClassNames(referenceClassName, exception.getException()); |
| } |
| } else { |
| referenceClass = PrivilegedAccessHelper.getClassForName(referenceClassName, true, classLoader); |
| } |
| } catch (ClassNotFoundException exception) { |
| throw ValidationException.classNotFoundWhileConvertingClassNames(referenceClassName, exception); |
| } |
| addClassIndicator(referenceClass, indicator); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Initialize the mapping. |
| */ |
| @Override |
| public void initialize(AbstractSession session) { |
| super.initialize(session); |
| initializeForeignKeys(session); |
| setFields(collectFields()); |
| if (usesIndirection()) { |
| for (DatabaseField field : this.fields) { |
| field.setKeepInRow(true); |
| } |
| } |
| if (getTypeField() != null) { |
| setTypeField(getDescriptor().buildField(getTypeField())); |
| } |
| if (shouldInitializeSelectionCriteria()) { |
| initializeSelectionCriteria(session); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * The foreign key names and their primary keys are converted to DatabaseField and stored. |
| */ |
| protected void initializeForeignKeys(AbstractSession session) { |
| HashMap newSourceToTargetQueryKeyNames = new HashMap(getSourceToTargetQueryKeyNames().size()); |
| Iterator iterator = getSourceToTargetQueryKeyNames().entrySet().iterator(); |
| while(iterator.hasNext()) { |
| Map.Entry entry = (Map.Entry)iterator.next(); |
| DatabaseField field = getDescriptor().buildField((DatabaseField)entry.getKey()); |
| newSourceToTargetQueryKeyNames.put(field, entry.getValue()); |
| } |
| this.sourceToTargetQueryKeyNames = newSourceToTargetQueryKeyNames; |
| } |
| |
| /** |
| * INTERNAL: |
| * Selection criteria is created with source foreign keys and target keys. |
| * This criteria is then used to read target records from the table. |
| */ |
| public void initializeSelectionCriteria(AbstractSession session) { |
| Expression selectionCriteria = null; |
| Expression expression; |
| |
| ExpressionBuilder expBuilder = new ExpressionBuilder(); |
| |
| Iterator sourceKeysEnum = getSourceToTargetQueryKeyNames().keySet().iterator(); |
| |
| while (sourceKeysEnum.hasNext()) { |
| DatabaseField sourceKey = (DatabaseField)sourceKeysEnum.next(); |
| String target = (String)this.getSourceToTargetQueryKeyNames().get(sourceKey); |
| expression = expBuilder.getParameter(sourceKey).equal(expBuilder.get(target)); |
| |
| if (selectionCriteria == null) { |
| selectionCriteria = expression; |
| } else { |
| selectionCriteria = expression.and(selectionCriteria); |
| } |
| } |
| |
| setSelectionCriteria(selectionCriteria); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isVariableOneToOneMapping() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| protected Object getPrimaryKeyForObject(Object object, AbstractSession session) { |
| return session.getId(object); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the type field classification through searching the indicators hashtable. |
| */ |
| @Override |
| public void preInitialize(AbstractSession session) throws DescriptorException { |
| super.preInitialize(session); |
| if (getTypeIndicatorTranslation().isEmpty()) { |
| return; |
| } |
| Class type = null; |
| for (Iterator typeValuesEnum = getTypeIndicatorTranslation().values().iterator(); |
| typeValuesEnum.hasNext() && (type == null);) { |
| Object value = typeValuesEnum.next(); |
| if ((value != Helper.NULL_VALUE) && (!(value instanceof Class))) { |
| type = value.getClass(); |
| } |
| } |
| |
| getTypeField().setType(type); |
| } |
| |
| /** |
| * INTERNAL: |
| * Rehash any maps based on fields. |
| * This is used to clone descriptors for aggregates, which hammer field names. |
| */ |
| @Override |
| public void rehashFieldDependancies(AbstractSession session) { |
| setSourceToTargetQueryKeyFields(Helper.rehashMap(getSourceToTargetQueryKeyNames())); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the class indicator associations. |
| */ |
| public void setClassIndicatorAssociations(Vector classIndicatorAssociations) { |
| setTypeIndicatorNameTranslation(new HashMap(classIndicatorAssociations.size() + 1)); |
| setTypeIndicatorTranslation(new HashMap((classIndicatorAssociations.size() * 2) + 1)); |
| for (Enumeration associationsEnum = classIndicatorAssociations.elements(); |
| associationsEnum.hasMoreElements();) { |
| Association association = (Association)associationsEnum.nextElement(); |
| Object classValue = association.getKey(); |
| if (classValue instanceof Class) { |
| // 904 projects will be a class type. |
| addClassIndicator((Class)association.getKey(), association.getValue()); |
| } else { |
| addClassNameIndicator((String)association.getKey(), association.getValue()); |
| } |
| } |
| } |
| |
| /** |
| * 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); |
| if (!fields.isEmpty()) { |
| setIsForeignKeyRelationship(true); |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * A foreign key from the source table and abstract query key from the interface descriptor are added to the |
| * mapping. This method is used if foreign key is not composite. |
| */ |
| public void setForeignQueryKeyName(String sourceForeignKeyFieldName, String targetQueryKeyName) { |
| addForeignQueryKeyName(sourceForeignKeyFieldName, targetQueryKeyName); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set a collection of the source to target query key/field associations. |
| */ |
| public void setSourceToTargetQueryKeyFieldAssociations(Vector sourceToTargetQueryKeyFieldAssociations) { |
| setSourceToTargetQueryKeyFields(new HashMap(sourceToTargetQueryKeyFieldAssociations.size() + 1)); |
| for (Enumeration associationsEnum = sourceToTargetQueryKeyFieldAssociations.elements(); |
| associationsEnum.hasMoreElements();) { |
| Association association = (Association)associationsEnum.nextElement(); |
| Object sourceField = new DatabaseField((String)association.getKey()); |
| String targetQueryKey = (String)association.getValue(); |
| getSourceToTargetQueryKeyNames().put(sourceField, targetQueryKey); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the source keys to target keys fields association. |
| */ |
| protected void setSourceToTargetQueryKeyFields(Map sourceToTargetQueryKeyNames) { |
| this.sourceToTargetQueryKeyNames = sourceToTargetQueryKeyNames; |
| } |
| |
| /** |
| * INTERNAL: |
| * This method set the typeField of the mapping to the parameter field |
| */ |
| public void setTypeField(DatabaseField typeField) { |
| this.typeField = typeField; |
| } |
| |
| /** |
| * PUBLIC: |
| * This method sets the name of the typeField of the mapping. |
| * The type field is used to store the type of object the relationship is referencing. |
| */ |
| public void setTypeFieldName(String typeFieldName) { |
| setTypeField(new DatabaseField(typeFieldName)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the typeIndicatorTranslations hashtable to the new Hashtable translations |
| */ |
| protected void setTypeIndicatorTranslation(Map translations) { |
| this.typeIndicatorTranslation = translations; |
| } |
| |
| /** |
| * INTERNAL: |
| * For avoiding classpath dependencies on the Mapping Workbench |
| */ |
| protected void setTypeIndicatorNameTranslation(Map translations) { |
| this.typeIndicatorNameTranslation = translations; |
| } |
| |
| /** |
| * 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. |
| AbstractRecord referenceRow = getIndirectionPolicy().extractReferenceRow(getAttributeValueFromObject(object)); |
| if (referenceRow != null) { |
| Object value = referenceRow.get(field); |
| |
| // Must ensure the classification to get a cache hit. |
| try { |
| value = session.getDatasourcePlatform().convertObject(value, getFieldClassification(field)); |
| } catch (ConversionException e) { |
| throw ConversionException.couldNotBeConverted(this, getDescriptor(), e); |
| } |
| |
| return value; |
| } |
| |
| //2.5.1.6 PWK. added to support batch reading on variable one to ones |
| Object referenceObject = getRealAttributeValueFromObject(object, session); |
| String queryKeyName = (String)getSourceToTargetQueryKeyNames().get(field); |
| ClassDescriptor objectDescriptor = session.getDescriptor(referenceObject.getClass()); |
| DatabaseField targetField = objectDescriptor.getObjectBuilder().getTargetFieldForQueryKeyName(queryKeyName); |
| |
| if (targetField == null) { |
| // Bug 326091 - return the type value if the field passed is the type indicator field |
| if (referenceObject != null && this.typeField != null && field.equals(this.typeField)) { |
| return getTypeForImplementor(referenceObject.getClass()); |
| } else { |
| return null; |
| } |
| } |
| |
| return objectDescriptor.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 |
| public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, CacheKey cacheKey, AbstractSession executionSession, 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); |
| } |
| } else if (!this.isCacheable && !isTargetProtected && cacheKey != null) { |
| return this.indirectionPolicy.buildIndirectObject(new ValueHolder(null)); |
| } |
| } |
| if (row.hasSopObject()) { |
| return getAttributeValueFromObject(row.getSopObject()); |
| } |
| // If any field in the foreign key is null then it means there are no referenced objects |
| for (DatabaseField field : getFields()) { |
| if (row.get(field) == null) { |
| return getIndirectionPolicy().nullValueFromRow(); |
| } |
| } |
| |
| if (getTypeField() != null) { |
| // If the query used batched reading, return a special value holder, |
| // or retrieve the object from the query property. |
| if (sourceQuery.isObjectLevelReadQuery() && (((ObjectLevelReadQuery)sourceQuery).isAttributeBatchRead(this.descriptor, getAttributeName()) |
| || (sourceQuery.isReadAllQuery() && shouldUseBatchReading()))) { |
| return batchedValueFromRow(row, ((ObjectLevelReadQuery)sourceQuery), cacheKey); |
| } |
| |
| //If the field is empty we cannot load the object because we do not know what class it will be |
| if (row.get(getTypeField()) == null) { |
| return getIndirectionPolicy().nullValueFromRow(); |
| } |
| Class implementerClass = (Class)getImplementorForType(row.get(getTypeField()), executionSession); |
| ReadObjectQuery query = (ReadObjectQuery)getSelectionQuery().clone(); |
| query.setReferenceClass(implementerClass); |
| query.setSelectionCriteria(getSelectionCriteria()); |
| query.setDescriptor(null);// Must set to null so the right descriptor is used |
| |
| if (sourceQuery.isObjectLevelReadQuery() && (sourceQuery.shouldCascadeAllParts() || (sourceQuery.shouldCascadePrivateParts() && isPrivateOwned()) || (sourceQuery.shouldCascadeByMapping() && this.cascadeRefresh)) ) { |
| query.setShouldRefreshIdentityMapResult(sourceQuery.shouldRefreshIdentityMapResult()); |
| query.setCascadePolicy(sourceQuery.getCascadePolicy()); |
| query.setShouldMaintainCache(sourceQuery.shouldMaintainCache()); |
| // For flashback. |
| if (((ObjectLevelReadQuery)sourceQuery).hasAsOfClause()) { |
| query.setAsOfClause(((ObjectLevelReadQuery)sourceQuery).getAsOfClause()); |
| } |
| |
| //CR #4365 - used to prevent infinit recursion on refresh object cascade all |
| query.setQueryId(sourceQuery.getQueryId()); |
| } |
| |
| return getIndirectionPolicy().valueFromQuery(query, row, executionSession); |
| } else { |
| return super.valueFromRow(row, joinManager, sourceQuery, cacheKey, executionSession, isTargetProtected, wasCacheUsed); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Get a value from the object and set that in the respective field of the row. |
| */ |
| protected void writeFromNullObjectIntoRow(AbstractRecord record) { |
| if (isReadOnly()) { |
| return; |
| } |
| if (isForeignKeyRelationship()) { |
| Enumeration foreignKeys = getForeignKeyFields().elements(); |
| while (foreignKeys.hasMoreElements()) { |
| record.put((DatabaseField)foreignKeys.nextElement(), null); |
| // EL Bug 319759 - if a field is null, then the update call cache should not be used |
| record.setNullValueInFields(true); |
| } |
| } |
| if (getTypeField() != null) { |
| record.put(getTypeField(), null); |
| record.setNullValueInFields(true); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Get a value from the object and set that in the respective field of the row. |
| * If the mapping id target foreign key, you must only write the type into the roe, the rest will be updated |
| * when the object itself is written |
| */ |
| @Override |
| public void writeFromObjectIntoRow(Object object, AbstractRecord record, AbstractSession session, WriteType writeType) { |
| if (isReadOnly()) { |
| return; |
| } |
| |
| Object referenceObject = getRealAttributeValueFromObject(object, session); |
| |
| if (referenceObject == null) { |
| writeFromNullObjectIntoRow(record); |
| } else { |
| if (isForeignKeyRelationship()) { |
| Enumeration sourceFields = getForeignKeyFields().elements(); |
| ClassDescriptor descriptor = session.getDescriptor(referenceObject.getClass()); |
| while (sourceFields.hasMoreElements()) { |
| DatabaseField sourceKey = (DatabaseField)sourceFields.nextElement(); |
| String targetQueryKey = (String)getSourceToTargetQueryKeyNames().get(sourceKey); |
| DatabaseField targetKeyField = descriptor.getObjectBuilder().getFieldForQueryKeyName(targetQueryKey); |
| if (targetKeyField == null) { |
| throw DescriptorException.variableOneToOneMappingIsNotDefinedProperly(this, descriptor, targetQueryKey); |
| } |
| Object referenceValue = descriptor.getObjectBuilder().extractValueFromObjectForField(referenceObject, targetKeyField, session); |
| // EL Bug 319759 - if a field is null, then the update call cache should not be used |
| if (referenceValue == null) { |
| record.setNullValueInFields(true); |
| } |
| record.put(sourceKey, referenceValue); |
| } |
| } |
| if (getTypeField() != null) { |
| record.put(getTypeField(), getTypeForImplementor(referenceObject.getClass())); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Get a value from the object and set that in the respective field of the row. |
| * If the mapping id target foreign key, you must only write the type into the roe, the rest will be updated |
| * when the object itself is written |
| */ |
| @Override |
| public void writeFromObjectIntoRowWithChangeRecord(ChangeRecord changeRecord, AbstractRecord record, AbstractSession session, WriteType writeType) { |
| if (isReadOnly()) { |
| return; |
| } |
| |
| ObjectChangeSet changeSet = (ObjectChangeSet)((ObjectReferenceChangeRecord)changeRecord).getNewValue(); |
| if (changeSet == null) { |
| writeFromNullObjectIntoRow(record); |
| } else { |
| Object referenceObject = changeSet.getUnitOfWorkClone(); |
| if (isForeignKeyRelationship()) { |
| Enumeration sourceFields = getForeignKeyFields().elements(); |
| ClassDescriptor descriptor = session.getDescriptor(referenceObject.getClass()); |
| while (sourceFields.hasMoreElements()) { |
| DatabaseField sourceKey = (DatabaseField)sourceFields.nextElement(); |
| String targetQueryKey = (String)getSourceToTargetQueryKeyNames().get(sourceKey); |
| DatabaseField targetKeyField = descriptor.getObjectBuilder().getFieldForQueryKeyName(targetQueryKey); |
| if (targetKeyField == null) { |
| throw DescriptorException.variableOneToOneMappingIsNotDefinedProperly(this, descriptor, targetQueryKey); |
| } |
| Object referenceValue = descriptor.getObjectBuilder().extractValueFromObjectForField(referenceObject, targetKeyField, session); |
| // EL Bug 319759 - if a field is null, then the update call cache should not be used |
| if (referenceValue == null) { |
| record.setNullValueInFields(true); |
| } |
| record.put(sourceKey, referenceValue); |
| } |
| } |
| if (getTypeField() != null) { |
| record.put(getTypeField(), getTypeForImplementor(referenceObject.getClass())); |
| } |
| } |
| } |
| |
| /** |
| * 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 record, AbstractSession session) { |
| writeFromNullObjectIntoRow(record); |
| } |
| |
| /** |
| * 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. |
| * If mapping overrides writeFromObjectIntoRowForShallowInsert method it must override this one, too. |
| */ |
| @Override |
| public void writeFromObjectIntoRowForUpdateAfterShallowInsert(Object object, AbstractRecord row, AbstractSession session, DatabaseTable table) { |
| if (!getFields().get(0).getTable().equals(table)) { |
| return; |
| } |
| writeFromObjectIntoRow(object, row, session, WriteType.UPDATE); |
| } |
| |
| /** |
| * 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 record, AbstractSession session) { |
| writeFromNullObjectIntoRow(record); |
| } |
| |
| /** |
| * INTERNAL: |
| * Get a value from the object and set that in the respective field of the row. |
| */ |
| @Override |
| public void writeFromObjectIntoRowForWhereClause(ObjectLevelModifyQuery query, AbstractRecord record) { |
| if (isReadOnly()) { |
| return; |
| } |
| Object object; |
| if (query.isDeleteObjectQuery()) { |
| object = query.getObject(); |
| } else { |
| object = query.getBackupClone(); |
| } |
| Object referenceObject = getRealAttributeValueFromObject(object, query.getSession()); |
| |
| if (referenceObject == null) { |
| writeFromNullObjectIntoRow(record); |
| } else { |
| if (isForeignKeyRelationship()) { |
| Enumeration sourceFields = getForeignKeyFields().elements(); |
| ClassDescriptor descriptor = query.getSession().getDescriptor(referenceObject.getClass()); |
| while (sourceFields.hasMoreElements()) { |
| DatabaseField sourceKey = (DatabaseField)sourceFields.nextElement(); |
| String targetQueryKey = (String)getSourceToTargetQueryKeyNames().get(sourceKey); |
| DatabaseField targetKeyField = descriptor.getObjectBuilder().getFieldForQueryKeyName(targetQueryKey); |
| if (targetKeyField == null) { |
| throw DescriptorException.variableOneToOneMappingIsNotDefinedProperly(this, descriptor, targetQueryKey); |
| } |
| Object referenceValue = descriptor.getObjectBuilder().extractValueFromObjectForField(referenceObject, targetKeyField, query.getSession()); |
| if (referenceValue == null) { |
| // EL Bug 319759 - if a field is null, then the update call cache should not be used |
| record.setNullValueInFields(true); |
| } |
| record.put(sourceKey, referenceValue); |
| } |
| } |
| if (getTypeField() != null) { |
| Object typeForImplementor = getTypeForImplementor(referenceObject.getClass()); |
| record.put(getTypeField(), typeForImplementor); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Write fields needed for insert into the template for with null values. |
| */ |
| @Override |
| public void writeInsertFieldsIntoRow(AbstractRecord record, AbstractSession session) { |
| writeFromNullObjectIntoRow(record); |
| } |
| } |