blob: 86e76783785f4291494b3e5d0a34c1c65f068100 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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<DatabaseField> enumtr = getForeignKeyFields().elements(); enumtr.hasMoreElements();) {
DatabaseField field = 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<ClassDescriptor> iterator = getReferenceDescriptor().getInterfacePolicy().getChildDescriptors().iterator();
if (iterator.hasNext()) {
ClassDescriptor firstChild = 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<DatabaseField> fieldsEnum = getForeignKeyFields().elements();
fieldsEnum.hasMoreElements();) {
fieldNames.addElement(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<DatabaseField> foreignKeys = getForeignKeyFields().elements();
while (foreignKeys.hasMoreElements()) {
record.put(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<DatabaseField> sourceFields = getForeignKeyFields().elements();
ClassDescriptor descriptor = session.getDescriptor(referenceObject.getClass());
while (sourceFields.hasMoreElements()) {
DatabaseField sourceKey = 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<DatabaseField> sourceFields = getForeignKeyFields().elements();
ClassDescriptor descriptor = session.getDescriptor(referenceObject.getClass());
while (sourceFields.hasMoreElements()) {
DatabaseField sourceKey = 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<DatabaseField> sourceFields = getForeignKeyFields().elements();
ClassDescriptor descriptor = query.getSession().getDescriptor(referenceObject.getClass());
while (sourceFields.hasMoreElements()) {
DatabaseField sourceKey = 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);
}
}