All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.mappings.foundation.AbstractDirectMapping Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 1998, 2024 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
//     11/13/2009-2.0  mobrien - 294765: MapKey keyType DirectToField processing
//       should return attributeClassification class in getMapKeyTargetType when
//       accessor.attributeField is null in the absence of a MapKey annotation
//     02/19/2015 - Rick Curtis
//       - 458877 : Add national character support
package org.eclipse.persistence.mappings.foundation;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor;
import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform;
import org.eclipse.persistence.internal.descriptors.DescriptorIterator;
import org.eclipse.persistence.internal.expressions.SQLSelectStatement;
import org.eclipse.persistence.internal.helper.ClassConstants;
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.identitymaps.CacheKey;
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.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.DirectToFieldChangeRecord;
import org.eclipse.persistence.internal.sessions.MergeManager;
import org.eclipse.persistence.internal.sessions.ObjectChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.mappings.converters.ClassInstanceConverter;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.mappings.converters.ObjectTypeConverter;
import org.eclipse.persistence.mappings.converters.SerializedObjectConverter;
import org.eclipse.persistence.mappings.converters.TypeConversionConverter;
import org.eclipse.persistence.mappings.querykeys.DirectQueryKey;
import org.eclipse.persistence.mappings.querykeys.QueryKey;
import org.eclipse.persistence.queries.DataReadQuery;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.QueryByExamplePolicy;
import org.eclipse.persistence.queries.ReadQuery;
import org.eclipse.persistence.queries.WriteObjectQuery;
import org.eclipse.persistence.sessions.CopyGroup;
import org.eclipse.persistence.sessions.Project;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.remote.DistributedSession;

import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Purpose: Maps an attribute to the corresponding database field type.
 * The list of field types that are supported by EclipseLink's direct to field mapping
 * is dependent on the relational database being used.
 * A converter can be used to convert between the object and data type if they do not match.
 *
 * @see Converter
 * @see ObjectTypeConverter
 * @see TypeConversionConverter
 * @see SerializedObjectConverter
 * @see ClassInstanceConverter
 *
 * @author Sati
 * @since TopLink/Java 1.0
 */
public abstract class AbstractDirectMapping extends AbstractColumnMapping implements MapKeyMapping {

    /** To specify the conversion type */
    protected transient Class attributeClassification;
    protected String attributeClassificationName;

    /** PERF: Also store object class of attribute in case of primitive. */
    protected transient Class attributeObjectClassification;

    /** Support specification of the value to use for null. */
    protected transient Object nullValue;

    protected DatabaseTable keyTableForMapKey = null;

    protected String fieldClassificationClassName = null;

    /** PERF: Avoid default null value conversion check if not default null value set in conversion manager. */
    protected boolean bypassDefaultNullValueCheck;

    /**
     * PERF: Indicates if this mapping's attribute is a simple atomic value and cannot be modified, only replaced.
     * This is a tri-state to allow user to set to true or false, as default is false but
     * some data-types such as Calendar or byte[] or converter types may be desired to be used as mutable.
     */
    protected Boolean isMutable;

    /**
     * Default constructor.
     */
    protected AbstractDirectMapping() {
        super();
    }

    /**
     * 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){
        if (selectionQuery.isObjectLevelReadQuery()){
            ((ObjectLevelReadQuery)selectionQuery).addAdditionalField(baseExpression.getField(getField()));
        } else if (selectionQuery.isDataReadQuery()){
            ((SQLSelectStatement) selectionQuery.getSQLStatement()).addField(baseExpression.getField(getField()));
        }
    }

    /**
     * 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) {
        if (!isReadOnly()){
            if (isUpdatable()){
                joinRow.put(getField(), null);
            }
        }
    }

    /**
     * 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 * DirectMappings are dealt with in their parent delete, so this is a no-op. */ @Override public void addKeyToDeletedObjectsList(Object object, Map deletedObjects) { } /** * PUBLIC: * Return true if the attribute for this mapping is a simple atomic value that cannot be modified, * only replaced. * This is false by default unless a mutable converter is used such as the SerializedObjectConverter. * This can be set to false in this case, or if a Calendar or byte[] is desired to be used as a mutable value it can be set to true. */ public boolean isMutable() { if (isMutable == null) { return false; } return isMutable; } /** * PUBLIC: * Return true if the attribute for this mapping is a simple atomic value that cannot be modified, * only replaced. * This is false by default unless a mutable converter is used such as the SerializedObjectConverter. * This can be set to false in this case, or if a Calendar or byte[] is desired to be used as a mutable value it can be set to true. */ public void setIsMutable(boolean isMutable) { if (isMutable) { this.isMutable = Boolean.TRUE; } else { this.isMutable = Boolean.FALSE; } } /** * INTERNAL: * Clone the attribute from the clone and assign it to the backup. */ @Override public void buildBackupClone(Object clone, Object backup, UnitOfWorkImpl unitOfWork) { buildClone(clone, null, backup, null, unitOfWork); } /** * INTERNAL: * Directly build a change record without comparison */ @Override public ChangeRecord buildChangeRecord(Object clone, ObjectChangeSet owner, AbstractSession session) { return internalBuildChangeRecord(getAttributeValueFromObject(clone), null, owner); } /** * INTERNAL: * Clone the attribute from the original and assign it to the clone. */ @Override public void buildClone(Object original, CacheKey cacheKey, Object clone, Integer refreshCascade, AbstractSession cloningSession) { buildCloneValue(original, clone, cloningSession); } /** * INTERNAL: * Extract value from the row and set the attribute to this value in the * working copy clone. * In order to bypass the shared cache when in transaction a UnitOfWork must * be able to populate working copies directly from the row. */ @Override public void buildCloneFromRow(AbstractRecord databaseRow, JoinedAttributeManager joinManager, Object clone, CacheKey sharedCacheKey, ObjectBuildingQuery sourceQuery, UnitOfWorkImpl unitOfWork, AbstractSession executionSession) { // Even though the correct value may exist on the original, we can't // make that assumption. It is easy to just build it again from the // row even if copy policy already copied it. // That optimization is lost. Object attributeValue = valueFromRow(databaseRow, joinManager, sourceQuery, sharedCacheKey, executionSession, true, null); setAttributeValueInObject(clone, attributeValue); } /** * INTERNAL: * Clone the attribute from the original and assign it to the clone. * If mutability is configured to be true, clone the attribute if it is an instance of * byte[], java.util.Calendar or java.util.Date (or their subclasses). */ public void buildCloneValue(Object original, Object clone, AbstractSession session) { Object attributeValue = getAttributeValueFromObject(original); attributeValue = buildCloneValue(attributeValue, session); setAttributeValueInObject(clone, attributeValue); } /** * INTERNAL: * Clone the actual value represented by this mapping. Do set the cloned value into the object. */ protected Object buildCloneValue(Object attributeValue, AbstractSession session) { Object newAttributeValue = attributeValue; if (isMutable() && attributeValue != null) { // EL Bug 252047 - Mutable attributes are not cloned when isMutable is enabled on a Direct Mapping if (attributeValue instanceof byte[]) { int length = ((byte[]) attributeValue).length; byte[] arrayCopy = new byte[length]; System.arraycopy(attributeValue, 0, arrayCopy, 0, length); newAttributeValue = arrayCopy; } else if (attributeValue instanceof Byte[]) { int length = ((Byte[]) attributeValue).length; Byte[] arrayCopy = new Byte[length]; System.arraycopy(attributeValue, 0, arrayCopy, 0, length); newAttributeValue = arrayCopy; } else if (attributeValue instanceof char[]) { int length = ((char[]) attributeValue).length; char[] arrayCopy = new char[length]; System.arraycopy(attributeValue, 0, arrayCopy, 0, length); newAttributeValue = arrayCopy; } else if (attributeValue instanceof Character[]) { int length = ((Character[]) attributeValue).length; Character[] arrayCopy = new Character[length]; System.arraycopy(attributeValue, 0, arrayCopy, 0, length); newAttributeValue = arrayCopy; } else if (attributeValue instanceof Date) { newAttributeValue = ((Date)attributeValue).clone(); } else if (attributeValue instanceof Calendar) { newAttributeValue = ((Calendar)attributeValue).clone(); } else { newAttributeValue = getObjectValue(getFieldValue(attributeValue, session), session); } } return newAttributeValue; } /** * INTERNAL: * Copy of the attribute of the object. * This is NOT used for unit of work but for templatizing an object. */ @Override public void buildCopy(Object copy, Object original, CopyGroup group) { buildCloneValue(original, copy, group.getSession()); } /** * 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 buildCloneValue(attributeValue, cloningSession); } /** * INTERNAL: * In case Query By Example is used, this method builds and returns an expression that * corresponds to a single attribute and it's value for a directToField mapping. */ @Override public Expression buildExpression(Object queryObject, QueryByExamplePolicy policy, Expression expressionBuilder, Map processedObjects, AbstractSession session) { String attributeName = this.getAttributeName(); Object attributeValue = this.getAttributeValueFromObject(queryObject); if (!policy.shouldIncludeInQuery(queryObject.getClass(), attributeName, attributeValue)) { //the attribute name and value pair is not to be included in the query. return null; } Expression expression = expressionBuilder.get(attributeName); if (attributeValue == null) { expression = policy.completeExpressionForNull(expression); } else { expression = policy.completeExpression(expression, attributeValue, attributeValue.getClass()); } return expression; } /** * 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: * Cascade discover and persist new objects during commit to the map key. */ @Override public void cascadeDiscoverAndPersistUnregisteredNewObjects(Object object, Map newObjects, Map unregisteredExistingObjects, Map visitedObjects, UnitOfWorkImpl uow, boolean getAttributeValueFromObject, Set cascadeErrors){ //objects referenced by this mapping are not registered as they have // no identity, this is a no-op. } /** * INTERNAL: * Cascade perform delete through mappings that require the cascade. */ @Override public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects, boolean getAttributeValueFromObject) { //objects referenced by this mapping are not registered as they have // no identity, this is a no-op. } /** * INTERNAL: * Cascade perform delete through mappings that require the cascade. */ @Override public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { //objects referenced by this mapping are not registered as they have // no identity, this is a no-op. } /** * INTERNAL: * Cascade registerNew for Create through mappings that require the cascade. */ @Override public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects, boolean getAttributeValueFromObject) { //objects referenced by this mapping are not registered as they have // no identity, this is a no-op. } /** * INTERNAL: * Cascade registerNew for Create through mappings that require the cascade. */ @Override public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { //objects referenced by this mapping are not registered as they have // no identity, this is a no-op. } /** * INTERNAL: * Compare the clone and backup clone values and return a change record if the value changed. */ @Override public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession session) { // same code as write from object into row for update if (owner.isNew()) { return internalBuildChangeRecord(getAttributeValueFromObject(clone), null, owner); } else if (!compareObjects(backUp, clone, session)) { Object oldValue = null; if (backUp != null && clone != backUp) { oldValue = getAttributeValueFromObject(backUp); } return internalBuildChangeRecord(getAttributeValueFromObject(clone), oldValue, owner); } return null; } /** * INTERNAL: * For mappings used as MapKeys in MappedKeyContainerPolicy, Delete the passed object if necessary. *

* This method is used for removal of private owned relationships * DirectMappings are dealt with in their parent delete, so this is a no-op. */ @Override public void deleteMapKey(Object objectDeleted, AbstractSession session){ } /** * INTERNAL: * Compare the attributes belonging to this mapping for the objects. */ @Override public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) { Object firstValue = getAttributeValueFromObject(firstObject); Object secondValue = getAttributeValueFromObject(secondObject); return compareObjectValues(firstValue, secondValue, session); } /** * INTERNAL: * Compare the attribute values. */ protected boolean compareObjectValues(Object firstValue, Object secondValue, AbstractSession session) { // PERF: Check identity before conversion. if (firstValue == secondValue) { return true; } if ((firstValue != null) && (secondValue != null)) { // PERF: Check equals first, as normally no change. // Also for serialization objects bytes may not be consistent, but equals may work (HashMap). if (firstValue.equals(secondValue)) { return true; } } // CR2114 - following two lines modified; getFieldValue() needs class as an argument firstValue = getFieldValue(firstValue, session); secondValue = getFieldValue(secondValue, session); // PERF: Check identity/nulls before special type comparison. if (firstValue == secondValue) { return true; } if ((firstValue == null) || (secondValue == null)) { return false; } // PERF: Check equals first, as normally no change. if (firstValue.equals(secondValue)) { return true; } return Helper.comparePotentialArrays(firstValue, secondValue); } /** * INTERNAL: * Convert all the class-name-based settings in this mapping to actual class-based settings * This method is implemented by subclasses as necessary. */ @Override public void convertClassNamesToClasses(ClassLoader classLoader){ super.convertClassNamesToClasses(classLoader); if (getAttributeClassificationName() != null) { Class attributeClass = null; try{ if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ try { attributeClass = AccessController.doPrivileged(new PrivilegedClassForName<>(getAttributeClassificationName(), true, classLoader)); } catch (PrivilegedActionException exception) { throw ValidationException.classNotFoundWhileConvertingClassNames(getAttributeClassificationName(), exception.getException()); } } else { attributeClass = PrivilegedAccessHelper.getClassForName(getAttributeClassificationName(), true, classLoader); } } catch (ClassNotFoundException exc){ throw ValidationException.classNotFoundWhileConvertingClassNames(getAttributeClassificationName(), exc); } setAttributeClassification(attributeClass); } if (fieldClassificationClassName != null){ Class fieldClassification = null; try { if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ try { fieldClassification = AccessController.doPrivileged(new PrivilegedClassForName<>(fieldClassificationClassName, true, classLoader)); } catch (PrivilegedActionException exception) { throw ValidationException.classNotFoundWhileConvertingClassNames(fieldClassificationClassName, exception.getException()); } } else { fieldClassification = PrivilegedAccessHelper.getClassForName(fieldClassificationClassName, true, classLoader); } } catch (ClassNotFoundException exc) { throw ValidationException.classNotFoundWhileConvertingClassNames(fieldClassificationClassName, exc); } catch (Exception e) { // Catches IllegalAccessException and InstantiationException throw ValidationException.classNotFoundWhileConvertingClassNames(fieldClassificationClassName, e); } setFieldClassification(fieldClassification); } } /** * INTERNAL: * Creates the Array of simple types used to recreate this map. */ @Override public Object createSerializableMapKeyInfo(Object key, AbstractSession session){ return key; // DirectToFields are already simple types. } /** * 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 createMapComponentsFromSerializableKeyInfo(Object[] keyInfo, AbstractSession session){ return Arrays.asList(keyInfo); // DirectToFields are already simple types. } /** * 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){ return keyInfo; } /** * 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) { Object key = dbRow.get(getField()); key = getObjectValue(key, session); return key; } /** * INTERNAL * Called when a DatabaseMapping is used to map the key in a collection and a join query is executed. Returns the key. */ @Override public Object createMapComponentFromJoinedRow(AbstractRecord dbRow, JoinedAttributeManager joinManger, ObjectBuildingQuery query, CacheKey parentCacheKey, AbstractSession session, boolean isTargetProtected) { return createMapComponentFromRow(dbRow, query, parentCacheKey, session, isTargetProtected); } /** * INTERNAL: * Create a query key that links to the map key. */ @Override public QueryKey createQueryKeyForMapKey() { DirectQueryKey queryKey = new DirectQueryKey(); queryKey.setField(getField()); return queryKey; } /** * 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 fields = new HashMap(); Object key = object; if (getConverter() != null){ key = getConverter().convertObjectValueToDataValue(key , session); } fields.put(getField(), key); return fields; } /** * INTERNAL: * Return any tables that will be required when this mapping is used as part of a join query. */ @Override public List getAdditionalTablesForJoinQuery() { List tables = new ArrayList(1); tables.add(getField().getTable()); return tables; } /** * PUBLIC: * Some databases do not properly support all of the base data types. For these databases, * the base data type must be explicitly specified in the mapping to tell EclipseLink to force * the instance variable value to that data type. */ @Override public Class getAttributeClassification() { return attributeClassification; } /** * INTERNAL: * Return the class name of the attribute type. * This is only used by the MW. */ public String getAttributeClassificationName() { if ((attributeClassificationName == null) && (attributeClassification != null)) { attributeClassificationName = attributeClassification.getName(); } return attributeClassificationName; } /** * INTERNAL: * Allows for subclasses to convert the attribute value. */ @Override public Object getObjectValue(Object fieldValue, Session session) { // PERF: Direct variable access. Object attributeValue = fieldValue; if ((fieldValue == null) && (this.nullValue != null)) {// Translate default null value return this.nullValue; } // Allow for user defined conversion to the object value. if (this.converter != null) { attributeValue = this.converter.convertDataValueToObjectValue(attributeValue, session); } else { // PERF: Avoid conversion check when not required. if ((attributeValue == null) || (attributeValue.getClass() != this.attributeObjectClassification)) { if ((attributeValue != null) || !this.bypassDefaultNullValueCheck) { try { attributeValue = session.getDatasourcePlatform().convertObject(attributeValue, this.attributeClassification); } catch (ConversionException e) { throw ConversionException.couldNotBeConverted(this, getDescriptor(), e); } } } } if (attributeValue == null) {// Translate default null value, conversion may have produced null. attributeValue = this.nullValue; } return attributeValue; } /** * INTERNAL: * Same as getObjectValue method, but without checking fieldValue's class. * Used in case the fieldValue class is already known to be the same as attributeClassification. */ public Object getObjectValueWithoutClassCheck(Object fieldValue, Session session) { if ((fieldValue == null) && (this.nullValue != null)) {// Translate default null value return this.nullValue; } // PERF: Direct variable access. Object attributeValue = fieldValue; // Allow for user defined conversion to the object value. if (this.converter != null) { attributeValue = this.converter.convertDataValueToObjectValue(attributeValue, session); } else { // PERF: Avoid conversion check when not required. if (attributeValue == null) { if (!this.bypassDefaultNullValueCheck) { try { attributeValue = session.getDatasourcePlatform().convertObject(null, this.attributeClassification); } catch (ConversionException e) { throw ConversionException.couldNotBeConverted(this, getDescriptor(), e); } } } } if (attributeValue == null) {// Translate default null value, conversion may have produced null. attributeValue = this.nullValue; } return attributeValue; } /** * INTERNAL: */ @Override public boolean isAbstractDirectMapping() { return true; } /** * INTERNAL: * Get the descriptor for this mapping * This method is potentially called when this mapping is used as a map key and * will return null since direct mappings do not have reference descriptors. */ @Override public ClassDescriptor getReferenceDescriptor(){ return null; } /** * 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) { // PERF: This method is a major performance code point, // so has been micro optimized and uses direct variable access. if (fieldToClassify.type != null) { return fieldToClassify.type; } else { if (hasConverter()) { return null; } else { // PERF: Ensure the object type is used for primitives. return Helper.getObjectClass(this.attributeClassification); } } } /** * ADVANCED: * Return the class type of the field value. * This can be used if field value differs from the object value, * has specific typing requirements such as usage of java.sql.Blob or NChar. */ public Class getFieldClassification() { if (getField() == null) { return null; } return getField().getType(); } /** * ADVANCED: * Set the class type of the field value. * This can be used if field value differs from the object value, * has specific typing requirements such as usage of java.sql.Blob or NChar. * This must be called after the field name has been set. */ public void setFieldClassification(Class fieldType) { getField().setType(fieldType); } /** * INTERNAL: * Set the name of the class that will be used for setFieldClassification and deploy time * Used internally by JPA deployment. * * @see #setFieldClassification(Class fieldType) */ public void setFieldClassificationClassName(String className){ this.fieldClassificationClassName = className; } /** * ADVANCED: * Set the JDBC type of the field value. * This can be used if field type does not correspond directly to a Java class type, * such as MONEY. * This is used for binding. */ public void setFieldType(int jdbcType) { getField().setSqlType(jdbcType); } /** * PUBLIC: * Name of the field this mapping represents. */ public String getFieldName() { return getField().getQualifiedName(); } /** * INTERNAL: * Convert the attribute value to a field value. * Process any converter if defined, and check for null values. */ @Override public Object getFieldValue(Object attributeValue, AbstractSession session) { // PERF: This method is a major performance code point, // so has been micro optimized and uses direct variable access. Object fieldValue = attributeValue; if ((this.nullValue != null) && (this.nullValue.equals(fieldValue))) { return null; } // Allow for user defined conversion to the object value. if (this.converter != null) { fieldValue = this.converter.convertObjectValueToDataValue(fieldValue, session); } Class fieldClassification = this.field.type; if (fieldClassification == null) { fieldClassification = getFieldClassification(this.field); } // PERF: Avoid conversion if not required. // EclipseLink bug 240407 - nulls not translated when writing to database if ((fieldValue == null) || (fieldClassification != fieldValue.getClass())) { if ((fieldValue != null) || !this.bypassDefaultNullValueCheck) { try { fieldValue = session.getPlatform(this.descriptor.getJavaClass()).convertObject(fieldValue, fieldClassification); } catch (ConversionException exception) { throw ConversionException.couldNotBeConverted(this, this.descriptor, exception); } } } return fieldValue; } /** * INTERNAL: * Return a Map of any foreign keys defined within the the MapKey. */ @Override public Map getForeignKeyFieldsForMapKey(){ return null; } /** * 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 getIdentityFieldsForMapKey(){ return getAllFieldsForMapKey(); } /** * INTERNAL: * Get all the fields for the map key. */ @Override public List getAllFieldsForMapKey(){ List fields = new ArrayList<>(1); fields.add(getField()); return fields; } /** * 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 null; } /** * PUBLIC: * Allow for the value used for null to be specified. * This can be used to convert database null values to application specific values, when null values * are not allowed by the application (such as in primitives). * Note: the default value for NULL is used on reads, writes, and query SQL generation */ public Object getNullValue() { return nullValue; } /** * INTERNAL: * Return the selection criteria necessary to select the target object when this mapping * is a map key. * DirectMappings do not need any additional selection criteria when they are map keys. */ @Override public Expression getAdditionalSelectionCriteriaForMapKey(){ return null; } /** * 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 object; } /** * INTERNAL: * Return the class this key mapping maps or the descriptor for it */ @Override public Class getMapKeyTargetType() { Class aClass = getAttributeAccessor().getAttributeClass(); // 294765: check the attributeClassification when the MapKey annotation is not specified if (null == aClass) { aClass = getAttributeClassification(); } if (null == aClass) { aClass = getField().getType(); } return aClass; } /** * INTERNAL: * Return the weight of the mapping, used to sort mappings to ensure that * DirectToField Mappings get merged first */ @Override public Integer getWeight() { return this.weight; } /** * INTERNAL: * Once descriptors are serialized to the remote session. All its mappings and reference descriptors are traversed. Usually * mappings are initialized and serialized reference descriptors are replaced with local descriptors if they already exist on the * remote session. */ @Override public void remoteInitialization(DistributedSession session) { if (!isRemotelyInitialized()) { super.remoteInitialization(session); if (this.attributeClassification == null) { this.attributeClassification = getAttributeAccessor().getAttributeClass(); } this.attributeObjectClassification = Helper.getObjectClass(this.attributeClassification); } } /** * INTERNAL: * Initialize the attribute classification. */ @Override public void preInitialize(AbstractSession session) throws DescriptorException { super.preInitialize(session); // Allow the attribute class to be set by the user. if (this.attributeClassification == null) { this.attributeClassification = getAttributeAccessor().getAttributeClass(); } this.attributeObjectClassification = Helper.getObjectClass(this.attributeClassification); // Initialize isMutable if not specified, default is false (assumes not mutable). if (this.isMutable == null) { if (hasConverter()) { setIsMutable(getConverter().isMutable()); } else { setIsMutable(false); } // If mapping a temporal type, use the project mutable default. if ((getAttributeClassification() != null) && (ClassConstants.UTILDATE.isAssignableFrom(getAttributeClassification()) || ClassConstants.CALENDAR.isAssignableFrom(getAttributeClassification()))) { setIsMutable(session.getProject().getDefaultTemporalMutable()); } } Map, Object> nullValues = session.getPlatform(this.descriptor.getJavaClass()).getConversionManager().getDefaultNullValues(); bypassDefaultNullValueCheck = (!this.attributeClassification.isPrimitive()) && ((nullValues == null) || (!nullValues.containsKey(this.attributeClassification))); } /** * INTERNAL: * The mapping is initialized with the given session. * This mapping is fully initialized after this. */ @Override public void initialize(AbstractSession session) throws DescriptorException { super.initialize(session); if (getField() == null) { session.getIntegrityChecker().handleError(DescriptorException.fieldNameNotSetInMapping(this)); } // Before potentially swapping out the field with an already built one, // set the JPA insertable and updatable flags based on the settings from // this mappings field. This must be done now. The reason for this code // is to cover the case where multiple mappings map to the same field. // One of those mappings must be write only, therefore, depending on the // initialization order we do not want to set the writable mapping as // non insertable and non updatable. isInsertable = getField().isInsertable(); isUpdatable = getField().isUpdatable(); if (keyTableForMapKey == null){ setField(getDescriptor().buildField(getField())); } else { setField(getDescriptor().buildField(getField(), keyTableForMapKey)); } setFields(collectFields()); if (hasConverter()) { getConverter().initialize(this, session); } // Must unwrap Struct types on WLS. if (getField().getSqlType() == java.sql.Types.STRUCT) { getDescriptor().setIsNativeConnectionRequired(true); } } /** * INTERNAL: * Build a change record. */ public ChangeRecord internalBuildChangeRecord(Object newValue, Object oldValue, ObjectChangeSet owner) { DirectToFieldChangeRecord changeRecord = new DirectToFieldChangeRecord(owner); changeRecord.setAttribute(getAttributeName()); changeRecord.setMapping(this); changeRecord.setNewValue(newValue); changeRecord.setOldValue(oldValue); return changeRecord; } /** * INTERNAL: */ @Override public boolean isDirectToFieldMapping() { 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){ if (iterator.shouldIterateOnPrimitives()) { iterator.iteratePrimitiveForMapping(element, this); } } /** * INTERNAL: * Merge changes from the source to the target object. */ @Override public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager, AbstractSession targetSession) { setAttributeValueInObject(target, buildCloneValue(((DirectToFieldChangeRecord)changeRecord).getNewValue(), mergeManager.getSession())); } /** * INTERNAL: * Merge changes from the source to the target object. This merge is only called when a changeSet for the target * does not exist or the target is uninitialized */ @Override public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager, AbstractSession targetSession) { // If merge into the unit of work, must only merge and raise the event is the value changed. if ((mergeManager.shouldMergeCloneIntoWorkingCopy() || mergeManager.shouldMergeCloneWithReferencesIntoWorkingCopy()) && !mergeManager.isForRefresh() && this.descriptor.getObjectChangePolicy().isObjectChangeTrackingPolicy()) { // if it didn't change then there will be no event Object attributeValue = getAttributeValueFromObject(source); Object targetAttribute = getAttributeValueFromObject(target); if (!compareObjectValues(attributeValue, targetAttribute, mergeManager.getSession())) { setAttributeValueInObject(target, buildCloneValue(attributeValue, mergeManager.getSession())); //set the value first, if the owner is new ( or aggregate) the change set may be created directly //from the target. this.descriptor.getObjectChangePolicy().raiseInternalPropertyChangeEvent(target, getAttributeName(), targetAttribute, attributeValue); } } else { setAttributeValueInObject(target, buildCloneValue(getAttributeValueFromObject(source), mergeManager.getSession())); } } /** * 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 { this.keyTableForMapKey = table; } /** * INTERNAL: * Making any mapping changes necessary to use a the mapping as a map key after initializing the mapping. */ @Override public void postInitializeMapKey(MappedKeyMapContainerPolicy policy) { if (getField().getType() == null) { getField().setType(getFieldClassification(getField())); } } /** * 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 !isReadOnly() && isUpdatable(); } /** * PUBLIC: * Some databases do not properly support all of the base data types. For these databases, * the base data type must be explicitly specified in the mapping to tell EclipseLink to force * the instance variable value to that data type */ public void setAttributeClassification(Class attributeClassification) { this.attributeClassification = attributeClassification; } /** * INTERNAL: * Set the name of the class for MW usage. */ public void setAttributeClassificationName(String attributeClassificationName) { this.attributeClassificationName = attributeClassificationName; } /** * PUBLIC: * Allow for the value used for null to be specified. * This can be used to convert database null values to application specific values, when null values * are not allowed by the application (such as in primitives). * Note: the default value for NULL is used on reads, writes, and query SQL generation */ public void setNullValue(Object nullValue) { this.nullValue = nullValue; } /** * INTERNAL: */ @Override public String toString() { return getClass().getName() + "[" + getAttributeName() + "-->" + getField() + "]"; } /** * INTERNAL: * Either create a new change record or update with the new value. This is used * by attribute change tracking. */ @Override public void updateChangeRecord(Object clone, Object newValue, Object oldValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) { DirectToFieldChangeRecord changeRecord = (DirectToFieldChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName()); if (changeRecord == null) { objectChangeSet.addChange(internalBuildChangeRecord(newValue, oldValue, objectChangeSet)); } else { changeRecord.setNewValue(newValue); } } /** * INTERNAL: * Return if this mapping supports change tracking. */ @Override public boolean isChangeTrackingSupported(Project project) { return !isMutable(); } /** * INTERNAL: * Return if this mapping requires its attribute value to be cloned. */ @Override public boolean isCloningRequired() { return isMutable() || getDescriptor().getCopyPolicy().buildsNewInstance(); } /** * INTERNAL: * Allow the key mapping to unwrap the object. */ @Override public Object unwrapKey(Object key, AbstractSession session){ return key; } /** * INTERNAL: * Allow for subclasses to perform validation. */ @Override public void validateBeforeInitialization(AbstractSession session) throws DescriptorException { if ((getFieldName() == null) || (getFieldName().isEmpty())) { session.getIntegrityChecker().handleError(DescriptorException.noFieldNameForMapping(this)); } } /** * INTERNAL: * Allow the key mapping to wrap the object. */ @Override public Object wrapKey(Object key, AbstractSession session){ return key; } /** * INTERNAL: * Get the value from the object for this mapping. */ @Override public Object valueFromObject(Object object, DatabaseField field, AbstractSession session) throws DescriptorException { return getFieldValue(getAttributeValueFromObject(object), session); } /** * 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). * @param original later the input to buildCloneFromRow */ @Override public void buildShallowOriginalFromRow(AbstractRecord databaseRow, Object original, JoinedAttributeManager joinManager, ObjectBuildingQuery query, AbstractSession executionSession) { readFromRowIntoObject(databaseRow, null, original, null, query, executionSession, true); } /** * INTERNAL: * Return the mapping's attribute value from the row. * The execution session is passed for the case of building a UnitOfWork clone * directly from a row, the session set in the query will not know which platform to use * for converting the value. Allows the correct session to be passed in. */ @Override public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery query, CacheKey cacheKey, AbstractSession executionSession, boolean isTargetProtected, Boolean[] wasCacheUsed) { if (this.descriptor.getCachePolicy().isProtectedIsolation()) { if (this.isCacheable && isTargetProtected && cacheKey != null && cacheKey.getInvalidationState() != CacheKey.CACHE_KEY_INVALID) { Object cached = cacheKey.getObject(); if (cached != null) { if (wasCacheUsed != null){ wasCacheUsed[0] = Boolean.TRUE; } Object attributeValue = getAttributeValueFromObject(cached); return buildCloneValue(attributeValue, executionSession); } } } if (row.hasSopObject()) { return getAttributeValueFromObject(row.getSopObject()); } // PERF: Direct variable access. Object fieldValue = row.get(getField()); Object attributeValue = getObjectValue(fieldValue, executionSession); return attributeValue; } /** * INTERNAL: * Returns the value for the mapping directly from the result-set. * PERF: Used for optimized object building. */ @Override public Object valueFromResultSet(ResultSet resultSet, ObjectBuildingQuery query, AbstractSession session, DatabaseAccessor accessor, ResultSetMetaData metaData, int columnNumber, DatabasePlatform platform) throws SQLException { if (this.attributeObjectClassification == ClassConstants.STRING) { Object val; if(platform.shouldUseGetSetNString()){ val = resultSet.getNString(columnNumber); }else { val = resultSet.getString(columnNumber); } return getObjectValueWithoutClassCheck(val, session); } else if (this.attributeObjectClassification == ClassConstants.LONG) { return getObjectValueWithoutClassCheck(resultSet.getLong(columnNumber), session); } else if (this.attributeObjectClassification == ClassConstants.INTEGER) { return getObjectValueWithoutClassCheck(resultSet.getInt(columnNumber), session); } Object fieldValue = accessor.getObject(resultSet, getField(), metaData, columnNumber, platform, true, session); return getObjectValue(fieldValue, session); } @Override protected abstract void writeValueIntoRow(AbstractRecord row, DatabaseField field, Object value); /** * 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 row, AbstractSession session, WriteType writeType) { if (isReadOnly() || (writeType.equals(WriteType.INSERT) && ! isInsertable()) || (writeType.equals(WriteType.UPDATE) && ! isUpdatable())) { return; } if (this.isPrimaryKeyMapping && !changeRecord.getOwner().isNew()) { throw ValidationException.primaryKeyUpdateDisallowed(changeRecord.getOwner().getClassName(), changeRecord.getAttribute()); } Object attributeValue = ((DirectToFieldChangeRecord)changeRecord).getNewValue(); Object fieldValue = getFieldValue(attributeValue, session); // EL Bug 319759 - if a field is null, then the update call cache should not be used if (fieldValue == null) { row.setNullValueInFields(true); } row.add(getField(), fieldValue); } /** * INTERNAL: * Get a value from the object and set that in the respective field of the row. */ @Override public void writeFromObjectIntoRow(Object object, AbstractRecord row, AbstractSession session, WriteType writeType) { if (isReadOnly() || (writeType.equals(WriteType.INSERT) && ! isInsertable()) || (writeType.equals(WriteType.UPDATE) && ! isUpdatable())) { return; } Object attributeValue = getAttributeValueFromObject(object); Object fieldValue = getFieldValue(attributeValue, session); // EL Bug 319759 - if a field is null, then the update call cache should not be used if (fieldValue == null) { row.setNullValueInFields(true); } writeValueIntoRow(row, getField(), fieldValue); } /** * INTERNAL: * Write the attribute value from the object to the row for update. */ @Override public void writeFromObjectIntoRowForUpdate(WriteObjectQuery query, AbstractRecord databaseRow) { if (query.getSession().isUnitOfWork()) { if (compareObjects(query.getBackupClone(), query.getObject(), query.getSession())) { return; } } super.writeFromObjectIntoRowForUpdate(query, databaseRow); } /** * INTERNAL: * Write fields needed for insert into the template for with null values. */ @Override public void writeInsertFieldsIntoRow(AbstractRecord databaseRow, AbstractSession session) { if (isInsertable() && ! isReadOnly()) { databaseRow.add(getField(), null); } } /** * INTERNAL: * Write fields needed for update into the template for with null values. * By default inserted fields are used. */ @Override public void writeUpdateFieldsIntoRow(AbstractRecord databaseRow, AbstractSession session) { if (isUpdatable() && ! isReadOnly()) { databaseRow.add(getField(), null); } } /** * INTERNAL: * Get fieldClassificationClassName. Value usually exist for fields with some kind of embedded converter like @Lob or @Temporal. */ public String getFieldClassificationClassName() { return this.fieldClassificationClassName; } }