/**
   * INTERNAL: Get a timestamp value from a result set. Overrides the default behavior to
   * specifically return a timestamp. Added to overcome an issue with the oracle 9.0.1.4 JDBC
   * driver.
   */
  public Object getObjectFromResultSet(
      ResultSet resultSet, int columnNumber, int type, AbstractSession session)
      throws java.sql.SQLException {
    // Bug#3381652 10G Drivers return sql.Date instead of timestamp on DATE field
    if ((type == Types.TIMESTAMP) || (type == Types.DATE)) {
      return resultSet.getTimestamp(columnNumber);
    } else if (type == OracleTypes.TIMESTAMPTZ) {
      TIMESTAMPTZ tsTZ = (TIMESTAMPTZ) resultSet.getObject(columnNumber);

      // Need to call timestampValue once here with the connection to avoid null point
      // exception later when timestampValue is called in converObject()
      if ((tsTZ != null) && (tsTZ.getLength() != 0)) {
        Connection connection = getConnection(session, resultSet.getStatement().getConnection());
        // Bug#4364359  Add a wrapper to overcome TIMESTAMPTZ not serializable as of jdbc 9.2.0.5
        // and 10.1.0.2.
        // It has been fixed in the next version for both streams
        return new TIMESTAMPTZWrapper(tsTZ, connection, isTimestampInGmt(connection));
      }
      return null;
    } else if (type == OracleTypes.TIMESTAMPLTZ) {
      // TIMESTAMPLTZ needs to be converted to Timestamp here because it requires the connection.
      // However the java object is not know here.  The solution is to store Timestamp and the
      // session timezone in a wrapper class, which will be used later in converObject().
      TIMESTAMPLTZ tsLTZ = (TIMESTAMPLTZ) resultSet.getObject(columnNumber);
      if ((tsLTZ != null) && (tsLTZ.getLength() != 0)) {
        Timestamp ts =
            TIMESTAMPLTZ.toTimestamp(
                getConnection(session, resultSet.getStatement().getConnection()), tsLTZ.toBytes());

        // Bug#4364359  Add a separate wrapper for TIMESTAMPLTZ.
        return new TIMESTAMPLTZWrapper(
            ts,
            ((OracleConnection) getConnection(session, resultSet.getStatement().getConnection()))
                .getSessionTimeZone());
      }
      return null;
    } else if (type == OracleTypes.OPAQUE) {
      try {
        Object result = resultSet.getObject(columnNumber);
        if (!(result instanceof OPAQUE)) {
          // Report Queries can cause result to not be an instance of OPAQUE.
          return result;
        }

        return getXMLTypeFactory().getString((OPAQUE) result);
      } catch (SQLException ex) {
        throw DatabaseException.sqlException(ex, null, session, false);
      }
    } else {
      Object value = super.getObjectFromResultSet(resultSet, columnNumber, type, session);
      if (type == OracleTypes_NCLOB) {
        value = convertObject(value, ClassConstants.STRING);
      }
      return value;
    }
  }
 /**
  * INTERNAL: Execute the query building the objects directly from the database result-set.
  *
  * @exception DatabaseException - an error has occurred on the database
  * @return object - the first object found or null if none.
  */
 protected Object executeObjectLevelReadQueryFromResultSet() throws DatabaseException {
   UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl) getSession();
   DatabaseAccessor accessor = (DatabaseAccessor) unitOfWork.getAccessor();
   DatabasePlatform platform = accessor.getPlatform();
   DatabaseCall call = (DatabaseCall) getCall().clone();
   call.setQuery(this);
   call.translate(this.translationRow, null, unitOfWork);
   Statement statement = null;
   ResultSet resultSet = null;
   boolean exceptionOccured = false;
   try {
     accessor.incrementCallCount(unitOfWork);
     statement = call.prepareStatement(accessor, this.translationRow, unitOfWork);
     resultSet = accessor.executeSelect(call, statement, unitOfWork);
     ResultSetMetaData metaData = resultSet.getMetaData();
     Vector results = new Vector();
     ObjectBuilder builder = this.descriptor.getObjectBuilder();
     while (resultSet.next()) {
       results.add(
           builder.buildWorkingCopyCloneFromResultSet(
               this,
               this.joinedAttributeManager,
               resultSet,
               unitOfWork,
               accessor,
               metaData,
               platform));
     }
     return results;
   } catch (SQLException exception) {
     exceptionOccured = true;
     DatabaseException commException =
         accessor.processExceptionForCommError(session, exception, call);
     if (commException != null) throw commException;
     throw DatabaseException.sqlException(exception, call, accessor, unitOfWork, false);
   } finally {
     try {
       if (resultSet != null) {
         resultSet.close();
       }
       if (statement != null) {
         accessor.releaseStatement(statement, call.getSQLString(), call, unitOfWork);
       }
     } catch (SQLException exception) {
       if (!exceptionOccured) {
         // in the case of an external connection pool the connection may be null after the
         // statement release
         // if it is null we will be unable to check the connection for a comm error and
         // therefore must return as if it was not a comm error.
         DatabaseException commException =
             accessor.processExceptionForCommError(session, exception, call);
         if (commException != null) throw commException;
         throw DatabaseException.sqlException(exception, call, accessor, session, false);
       }
     }
   }
 }
  /** INTERNAL: Build a ADT structure from the row data. */
  public Struct buildStructureFromRow(
      AbstractRecord row, AbstractSession session, java.sql.Connection connection)
      throws DatabaseException {
    Struct structure;
    boolean reconnected = false;

    try {
      if (connection == null) {
        ((DatabaseAccessor) session.getAccessor()).incrementCallCount(session);
        reconnected = true;
        connection = ((DatabaseAccessor) session.getAccessor()).getConnection();
      }

      Object[] fields = new Object[getOrderedFields().size()];
      for (int index = 0; index < getOrderedFields().size(); index++) {
        DatabaseField field = (DatabaseField) getOrderedFields().elementAt(index);
        fields[index] = row.get(field);
      }

      structure =
          session.getPlatform().createStruct(getStructureName(), fields, session, connection);
    } catch (java.sql.SQLException exception) {
      throw DatabaseException.sqlException(exception, session, false);
    } finally {
      if (reconnected) {
        ((DatabaseAccessor) session.getAccessor()).decrementCallCount();
      }
    }

    return structure;
  }
  /**
   * 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: Build and return the appropriate field value for the specified set of nested rows.
   * The database better be expecting an ARRAY. It looks like we can ignore inheritance here....
   */
  public Object buildFieldValueFromNestedRows(
      Vector nestedRows, String structureName, AbstractSession session) throws DatabaseException {
    Object[] fields = new Object[nestedRows.size()];
    java.sql.Connection connection = ((DatabaseAccessor) session.getAccessor()).getConnection();
    boolean reconnected = false;

    try {
      if (connection == null) {
        ((DatabaseAccessor) session.getAccessor()).incrementCallCount(session);
        reconnected = true;
        connection = ((DatabaseAccessor) session.getAccessor()).getConnection();
      }

      int i = 0;
      for (Enumeration stream = nestedRows.elements(); stream.hasMoreElements(); ) {
        AbstractRecord nestedRow = (AbstractRecord) stream.nextElement();
        fields[i++] = this.buildStructureFromRow(nestedRow, session, connection);
      }

      return session.getPlatform().createArray(structureName, fields, session, connection);
    } catch (java.sql.SQLException exception) {
      throw DatabaseException.sqlException(exception, session, false);
    } finally {
      if (reconnected) {
        ((DatabaseAccessor) session.getAccessor()).decrementCallCount();
      }
    }
  }
 /**
  * INTERNAL: Build the appropriate field value for the specified set of direct values. The
  * database better be expecting an ARRAY.
  */
 public Object buildFieldValueFromDirectValues(
     Vector directValues, String elementDataTypeName, AbstractSession session)
     throws DatabaseException {
   Object[] fields = Helper.arrayFromVector(directValues);
   try {
     ((DatabaseAccessor) session.getAccessor()).incrementCallCount(session);
     java.sql.Connection connection = ((DatabaseAccessor) session.getAccessor()).getConnection();
     return session.getPlatform().createArray(elementDataTypeName, fields, session, connection);
   } catch (java.sql.SQLException ex) {
     throw DatabaseException.sqlException(ex, session, false);
   } finally {
     ((DatabaseAccessor) session.getAccessor()).decrementCallCount();
   }
 }
  /**
   * INTERNAL: Build a row representation from the ADT structure field array. TopLink will then
   * build the object from the row.
   */
  public AbstractRecord buildRowFromStructure(Struct structure) throws DatabaseException {
    Object[] attributes;
    try {
      attributes = structure.getAttributes();
    } catch (java.sql.SQLException exception) {
      throw DatabaseException.sqlException(exception);
    }

    if (attributes != null) {
      for (int i = 0; i < attributes.length; i++) {
        if (attributes[i] instanceof Array) {
          attributes[i] =
              ObjectRelationalDataTypeDescriptor.buildArrayObjectFromArray(attributes[i]);
        } else if (attributes[i] instanceof Struct) {
          attributes[i] =
              ObjectRelationalDataTypeDescriptor.buildArrayObjectFromStruct(attributes[i]);
        }
      }
    }

    return buildNestedRowFromFieldValue(attributes);
  }
 /** INTERNAL: Build array of objects for Struct data type. */
 public static Object buildArrayObjectFromStruct(Object structure) throws DatabaseException {
   Object[] attributes = null;
   if (structure == null) {
     return structure;
   }
   try {
     attributes = ((Struct) structure).getAttributes();
   } catch (java.sql.SQLException exception) {
     throw DatabaseException.sqlException(exception);
   }
   if (attributes == null) {
     return null;
   } else {
     for (int i = 0; i < attributes.length; i++) {
       if (attributes[i] instanceof Array) {
         attributes[i] = buildArrayObjectFromArray(attributes[i]);
       }
       if (attributes[i] instanceof Struct) {
         attributes[i] = buildArrayObjectFromStruct(attributes[i]);
       }
     }
   }
   return attributes;
 }
 /** INTERNAL: Build array of objects for Array data type. */
 public static Object buildArrayObjectFromArray(Object array) throws DatabaseException {
   Object[] objects = null;
   if (array == null) {
     return array;
   }
   try {
     objects = (Object[]) ((Array) array).getArray();
   } catch (java.sql.SQLException ex) {
     throw DatabaseException.sqlException(ex);
   }
   if (objects == null) {
     return null;
   } else {
     for (int i = 0; i < objects.length; i++) {
       if (objects[i] instanceof Array) {
         objects[i] = buildArrayObjectFromArray(objects[i]);
       }
       if (objects[i] instanceof Struct) {
         objects[i] = buildArrayObjectFromStruct(objects[i]);
       }
     }
   }
   return objects;
 }
  /** INTERNAL: Allow for conversion from the Oracle type to the Java type. */
  public Object convertObject(Object sourceObject, Class javaClass)
      throws ConversionException, DatabaseException {
    if ((javaClass == null) || ((sourceObject != null) && (sourceObject.getClass() == javaClass))) {
      return sourceObject;
    }

    Object valueToConvert = sourceObject;

    // Used in Type Conversion Mapping on write
    if ((javaClass == TIMESTAMPTypes.TIMESTAMP_CLASS)
        || (javaClass == TIMESTAMPTypes.TIMESTAMPLTZ_CLASS)) {
      return sourceObject;
    }

    if (javaClass == TIMESTAMPTypes.TIMESTAMPTZ_CLASS) {
      if (sourceObject instanceof java.util.Date) {
        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(((java.util.Date) sourceObject).getTime());
        return cal;
      } else {
        return sourceObject;
      }
    }

    if (javaClass == XMLTYPE) {
      // Don't convert to XMLTypes. This will be done by the
      // XMLTypeBindCallCustomParameter to ensure the correct
      // Connection is used
      return sourceObject;
    }

    // Added to overcome an issue with the oracle 9.0.1.1.0 JDBC driver.
    if (sourceObject instanceof TIMESTAMP) {
      try {
        valueToConvert = ((TIMESTAMP) sourceObject).timestampValue();
      } catch (SQLException exception) {
        throw DatabaseException.sqlException(exception);
      }
    } else if (sourceObject instanceof TIMESTAMPTZWrapper) {
      // Bug#4364359 Used when database type is TIMESTAMPTZ.  Timestamp and session timezone are
      // wrapped
      // in TIMESTAMPTZWrapper.  Separate Calendar from any other types.
      if (((javaClass == ClassConstants.CALENDAR)
          || (javaClass == ClassConstants.GREGORIAN_CALENDAR))) {
        try {
          return TIMESTAMPHelper.buildCalendar((TIMESTAMPTZWrapper) sourceObject);
        } catch (SQLException exception) {
          throw DatabaseException.sqlException(exception);
        }
      } else {
        // If not using native sql, Calendar will be converted to Timestamp just as
        // other date time types
        valueToConvert = ((TIMESTAMPTZWrapper) sourceObject).getTimestamp();
      }
    } else if (sourceObject instanceof TIMESTAMPLTZWrapper) {
      // Bug#4364359 Used when database type is TIMESTAMPLTZ.  Timestamp and session timezone id are
      // wrapped
      // in TIMESTAMPLTZWrapper.  Separate Calendar from any other types.
      if (((javaClass == ClassConstants.CALENDAR)
          || (javaClass == ClassConstants.GREGORIAN_CALENDAR))) {
        try {
          return TIMESTAMPHelper.buildCalendar((TIMESTAMPLTZWrapper) sourceObject);
        } catch (SQLException exception) {
          throw DatabaseException.sqlException(exception);
        }
      } else {
        // If not using native sql, Calendar will be converted to Timestamp just as
        // other date time types
        valueToConvert = ((TIMESTAMPLTZWrapper) sourceObject).getTimestamp();
      }
    }

    return super.convertObject(valueToConvert, javaClass);
  }
  /**
   * INTERNAL: Applies customization to connection. Called only if connection is not already
   * customized (isActive()==false). The method may throw SQLException wrapped into
   * DatabaseException. isActive method called after this method should return true only in case the
   * connection was actually customized.
   */
  public void customize() {
    // Lazily initialize proxy properties - customize method may be never called
    // in case of ClientSession using external connection pooling.
    if (proxyProperties == null) {
      buildProxyProperties();
    }

    Connection connection = accessor.getConnection();
    if (connection instanceof OracleConnection) {
      oracleConnection = (OracleConnection) connection;
    } else {
      connection = session.getServerPlatform().unwrapConnection(connection);
      if (connection instanceof OracleConnection) {
        oracleConnection = (OracleConnection) connection;
      } else {
        throw ValidationException.oracleJDBC10_1_0_2ProxyConnectorRequiresOracleConnection();
      }
    }
    try {
      clearConnectionCache();
      Object[] args = null;
      if (this.session.shouldLog(SessionLog.FINEST, SessionLog.CONNECTION)) {
        Properties logProperties = proxyProperties;
        if (proxyProperties.containsKey(OracleConnection.PROXY_USER_PASSWORD)) {
          logProperties = (Properties) proxyProperties.clone();
          logProperties.setProperty(OracleConnection.PROXY_USER_PASSWORD, "******");
        }
        args = new Object[] {oracleConnection, logProperties};
      }
      if (oracleConnection.isProxySession()) {
        // Unexpectedly oracleConnection already has a proxy session - probably it was not closed
        // when connection was returned back to connection pool.
        // That may happen on jta transaction rollback (especially triggered outside of user's
        // thread - such as timeout)
        // when beforeCompletion is never issued
        // and application server neither closes proxySession nor allows access to connection in
        // afterCompletion.
        try {
          if (args != null) {
            ((AbstractSession) this.session)
                .log(
                    SessionLog.FINEST,
                    SessionLog.CONNECTION,
                    "proxy_connection_customizer_already_proxy_session",
                    args);
          }
          oracleConnection.close(OracleConnection.PROXY_SESSION);
        } catch (SQLException exception) {
          // Ignore
          this.session
              .getSessionLog()
              .logThrowable(SessionLog.WARNING, SessionLog.CONNECTION, exception);
        }
      }
      oracleConnection.openProxySession(proxyType, proxyProperties);
      // 12c driver will default to an autoCommit setting of true on calling openProxySession
      oracleConnection.setAutoCommit(false);
      if (args != null) {
        ((AbstractSession) this.session)
            .log(
                SessionLog.FINEST,
                SessionLog.CONNECTION,
                "proxy_connection_customizer_opened_proxy_session",
                args);
      }
    } catch (SQLException exception) {
      oracleConnection = null;
      throw DatabaseException.sqlException(exception);
    } catch (NoSuchMethodError noSuchMethodError) {
      oracleConnection = null;
      throw ValidationException.oracleJDBC10_1_0_2ProxyConnectorRequiresOracleConnectionVersion();
    }
  }
  /**
   * INTERNAL: Build and return the nested rows from the specified field value. This method allows
   * the field value to be an ARRAY containing other structures such as arrays or Struct, or direct
   * values.
   */
  public static Object buildContainerFromArray(
      Array fieldValue, ObjectRelationalDatabaseField arrayField, AbstractSession session)
      throws DatabaseException {
    if (arrayField.getType() == null) {
      return fieldValue;
    }
    Object[] objects = null;
    try {
      objects = (Object[]) fieldValue.getArray();
    } catch (java.sql.SQLException ex) {
      throw DatabaseException.sqlException(ex, session, false);
    }
    if (objects == null) {
      return null;
    }

    boolean isNestedStructure = false;
    ObjectRelationalDataTypeDescriptor ord = null;
    DatabaseField nestedType = null;
    if (arrayField != null) {
      nestedType = arrayField.getNestedTypeField();
      if ((nestedType != null) && nestedType.getSqlType() == Types.STRUCT) {
        ClassDescriptor descriptor = session.getDescriptor(nestedType.getType());
        if ((descriptor != null) && (descriptor.isObjectRelationalDataTypeDescriptor())) {
          // this is used to convert non-null objects passed through stored procedures and custom
          // SQL to structs
          ord = (ObjectRelationalDataTypeDescriptor) descriptor;
        }
      } else if ((nestedType != null) && (nestedType instanceof ObjectRelationalDatabaseField)) {
        isNestedStructure = true;
      }
    }
    // handle ARRAY conversions
    ReadObjectQuery query = new ReadObjectQuery();
    query.setSession(session);
    ContainerPolicy cp = ContainerPolicy.buildPolicyFor(arrayField.getType());
    Object container = cp.containerInstance(objects.length);
    for (int i = 0; i < objects.length; i++) {
      Object arrayValue = objects[i];
      if (arrayValue == null) {
        return null;
      }
      if (ord != null) {
        AbstractRecord nestedRow = ord.buildRowFromStructure((Struct) arrayValue);
        ClassDescriptor descriptor = ord;
        if (descriptor.hasInheritance()) {
          Class newElementClass =
              descriptor.getInheritancePolicy().classFromRow(nestedRow, session);
          if (!descriptor.getJavaClass().equals(newElementClass)) {
            descriptor = session.getDescriptor(newElementClass);
            if (descriptor == null) {
              descriptor = ord;
            }
          }
        }
        arrayValue = descriptor.getObjectBuilder().buildNewInstance();
        descriptor
            .getObjectBuilder()
            .buildAttributesIntoObject(arrayValue, nestedRow, query, null, false);
      } else if (isNestedStructure && (arrayValue instanceof Array)) {
        arrayValue =
            buildContainerFromArray(
                (Array) arrayValue, (ObjectRelationalDatabaseField) nestedType, session);
      }

      cp.addInto(arrayValue, container, session);
    }
    return container;
  }