/** 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) {
      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.
   */
  public Object valueFromRow(
      AbstractRecord row,
      JoinedAttributeManager joinManager,
      ObjectBuildingQuery query,
      AbstractSession executionSession)
      throws DatabaseException {
    Ref ref = (Ref) row.get(getField());

    if (ref == null) {
      return null;
    }

    Struct struct;
    try {
      ((DatabaseAccessor) executionSession.getAccessor()).incrementCallCount(executionSession);
      java.sql.Connection connection =
          ((DatabaseAccessor) executionSession.getAccessor()).getConnection();
      struct =
          (Struct) executionSession.getPlatform().getRefValue(ref, executionSession, connection);
    } catch (java.sql.SQLException exception) {
      throw DatabaseException.sqlException(exception, executionSession, false);
    }
    AbstractRecord targetRow =
        ((ObjectRelationalDataTypeDescriptor) getReferenceDescriptor())
            .buildRowFromStructure(struct);
    ((DatabaseAccessor) executionSession.getAccessor()).decrementCallCount();

    return getReferenceDescriptor().getObjectBuilder().buildObject(query, targetRow, joinManager);
  }
  /**
   * 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.
   *
   * <p>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 implementor for a specified type */
  protected Object getImplementorForType(Object type, AbstractSession session) {
    if (type == null) {
      return getTypeIndicatorTranslation().get(Helper.getNullWrapper());
    }

    // 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);
  }
  /** INTERNAL: Initialize the mapping. */
  public void initialize(AbstractSession session) throws DescriptorException {
    setReferenceDescriptor(session.getDescriptor(getReferenceClass()));

    if (referenceDescriptor == null) {
      throw DescriptorException.descriptorIsMissing(getReferenceClass().getName(), this);
    }

    // For bug 2730536 convert the field to be an ObjectRelationalDatabaseField.
    ObjectRelationalDatabaseField field = (ObjectRelationalDatabaseField) getField();
    field.setSqlType(java.sql.Types.REF);
    if (referenceDescriptor instanceof ObjectRelationalDataTypeDescriptor) {
      field.setSqlTypeName(
          ((ObjectRelationalDataTypeDescriptor) referenceDescriptor).getStructureName());
    }

    setField(getDescriptor().buildField(getField()));
    setFields(collectFields());

    // Ref mapping requires native connection in WLS as the Ref is wrapped.
    getDescriptor().setIsNativeConnectionRequired(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 writeFromObjectIntoRowWithChangeRecord(
      ChangeRecord changeRecord, AbstractRecord record, AbstractSession session) {
    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);
          record.put(sourceKey, referenceValue);
        }
      }
      if (getTypeField() != null) {
        record.put(getTypeField(), getTypeForImplementor(referenceObject.getClass()));
      }
    }
  }
 /** INTERNAL: */
 @Override
 protected Object getPrimaryKeyForObject(Object object, AbstractSession session) {
   return session.getId(object);
 }
 /**
  * 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);
 }