///////////////////////////////////////////////////////////////////////////////
//                                                                             
// JTOpen (IBM Toolbox for Java - OSS version)                                 
//                                                                             
// Filename: JDCursor.java
//                                                                             
// The source code contained herein is licensed under the IBM Public License   
// Version 1.0, which has been approved by the Open Source Initiative.         
// Copyright (C) 1997-2001 International Business Machines Corporation and     
// others. All rights reserved.                                                
//                                                                             
///////////////////////////////////////////////////////////////////////////////

package com.ibm.as400.access;

import java.sql.ResultSet;
import java.sql.SQLException;



/**
This class implements a cursor on the IBM i system.  NOTE: Creating this
object does not explicitly create the cursor on the system.
**/
class JDCursor
{
  // Open attributes.
  private static final int    OPEN_READONLY_      = 0x80;
  private static final int    OPEN_ALL_           = 0xF0;
  private static final int    FULL_OPEN_          = 0x01;   // @W1a



  // Reuse flags.  These describe how to close a cursor.
  static final int            REUSE_NO            = 0xF0;
  static final int            REUSE_YES           = 0xF1;
  static final int            REUSE_RESULT_SET    = 0xF2;



  private boolean             closed_;
  private AS400JDBCConnection connection_;
  private int                 id_;
  private boolean             lazyClose_;                         // @E2A
  private String              name_;
  private int                 concurrency_;                       // @E4A
  private int                 holdable_ = -1;            //@cur
  private int                 scrollable_ = -1;          //@cur
  private int                 updatable_ = -1;           //@cur (if on 550 hostserver, can use this instead of concurrency_)
  private int                 sensitive_ = -1;           //@cur
  private int                 isolationLevel_ = -1;      //@isol  
  private boolean             extendedMetaData_ = false; 
  private DBReplyRequestedDS openReply = null;
  private DBExtendedColumnDescriptors extendedColumnDescriptors_ = null; 

/**
Constructs a JDCursor object.

@param  connection      Connection to the system.
@param   id             The id.
@param  name            Cursor name.
@param  concurrency     The concurrency
**/
  JDCursor (AS400JDBCConnection connection,
            int id,
            String name,
            // @E3D boolean lazyClose,
            int concurrency)                                      // @E4A
  throws  SQLException                                            // @E6A
  {
    closed_     = true;
    concurrency_ = concurrency;                                 // @E4A
    connection_ = connection;
    id_         = id;
    name_       = name;

    lazyClose_  = connection_.getProperties().getBoolean(JDProperties.LAZY_CLOSE); // @E2A
    // @E3D lazyClose_  = lazyClose;                                                            // @E3A
  }



/**
Closes the cursor.

@param  reuseFlag   One of REUSE_*.

@exception  SQLException    If an error occurs.
**/
//
// Implementation note:
//
// It is a requirement to not get replies during a finalize()
// method.  Since Statement.finalize() calls this method,
// this requirement applies here, too.
//
  void close (int reuseFlag)
  throws SQLException
  {
    // Close the cursor for good.  This makes sure that
    // the cursor is not left around.
    DBSQLRequestDS request = null; //@P0A
    boolean keepDS = false; //@P1A
    try
    {
      request = DBDSPool.getDBSQLRequestDS (DBSQLRequestDS.FUNCTIONID_CLOSE, id_, 0, 0); //@P0C
      request.setReuseIndicator (reuseFlag);

      
      if (lazyClose_)                                             // @E2A
      {
        keepDS = true; //@P1A
        connection_.sendAndHold(request, id_);                  // @E2A
      }
      else
      {                                                        // @E2A
        connection_.send (request, id_);
      }
    }
    catch (DBDataStreamException e)
    {
      JDError.throwSQLException (JDError.EXC_INTERNAL, e);        // @E5C
    }
    finally
    {
      if (request != null && !keepDS)  { request.returnToPool(); request = null; }  //@P1C
    }

    if (openReply != null) {
      openReply.returnToPool();  openReply = null; 
    }
    
    closed_ = true;

    if (JDTrace.isTraceOn())
    {
      JDTrace.logInformation (this, "Closing with reuse flag = " + reuseFlag);
      JDTrace.logClose  (this);
    }
  }



/**
Returns the cursor name.

@return     The cursor name.
**/
  String getName ()
  {
    return name_;
  }



/**
Returns the open attributes appropriate for the the
SQL statement.

@param  sqlStatement        a SQL statement.
@param  blockCriteria       block criteria.

@return the open attributes.

@exception  SQLException    If an error occurrs.
**/
//
// Implementation note: Cursors perform better on
// the system when read only, so set the open
// attributes to read only in as many cases as
// possible.  (Of course, not when the user needs
// to update using the cursor.)
//
  int getOpenAttributes (JDSQLStatement sqlStatement,
                         String blockCriteria)
  throws SQLException
  {
    int returnValue = OPEN_ALL_;                     // @W1a

    // If we are opening a cursor on a subsequent
    // result set returned by a stored procedure,
    // then it is read only.
    if (sqlStatement == null)
      returnValue = OPEN_READONLY_;                // @W1c

    // If we are opening a cursor on a result set
    // returned by a stored procedure, then it is
    // read only.
    else if (sqlStatement.isProcedureCall ())        // @W1c
      returnValue = OPEN_READONLY_;                // @W1c

    // For SELECTs, the cursor is read only when
    // the cursor or connection is read only and
    // when we are record blocking.  Note that record
    // blocking implies a read only cursor.    

    // Change for J3 -- a system change in mod 5 allows updatable cursors 
    // even when the select statement does not specific "for update".  The
    // J3 change takes this into consideration.  The J3 change is to add
    // !(concurrency_ == ResultSet.CONCUR_UPDATABLE)) to the check.
    else if (
            sqlStatement.isSelect ()               // @W1c
            && (! blockCriteria.equalsIgnoreCase (JDProperties.BLOCK_CRITERIA_NONE))
            && ((connection_.isReadOnly()) || ((! sqlStatement.isForUpdate ()) && (!(concurrency_ == ResultSet.CONCUR_UPDATABLE))))  // @J3C
            )
      returnValue = OPEN_READONLY_;                // @W1c

    // the "ServerLevel > 9" in the following check makes sure we are running 
    // to a v5r1 or later version of the AS/400.                                                   
    if ((connection_.getProperties().getBoolean(JDProperties.FULL_OPEN)) && // @W1a
        (connection_.getServerFunctionalLevel() >= 9))                      // @W1a
      returnValue = returnValue | FULL_OPEN_;       // @W1a

    // return OPEN_ALL_;                             // @W1d
    return returnValue;                              // @W1a
  }



// @E1A @E4C
/**
Returns the cursor concurrency.

@return The cursor concurrency.
**/
  int getConcurrency()
  {
    return concurrency_;
  }



/**
Indicates if the cursor is closed.

@return     true if the cursor is closed; false otherwise.
**/
  boolean isClosed ()
  {
    return closed_;
  }

/**
 * Set the extendedColumnDescriptrs from the reply 
 * @param reply
 */
  //@P6A
  void setExtendedColumnDescriptorsFromReply( DBReplyRequestedDS reply) {
  if (extendedMetaData_) { 
  extendedColumnDescriptors_  = reply.getExtendedColumnDescriptors();
  } else { 
    extendedColumnDescriptors_ =null; 
  }
  }


/**
Opens the cursor and describes the data format.

@param      openAttributes  The open attributes.
@param      scrollable      true if the cursor should be
                            scrollable, false otherwise.
@return     The data format.

@exception  SQLException    If an error occurs.
**/
  DBDataFormat openDescribe (int openAttributes,
                             int resultSetType)         //@KBC
  throws SQLException
  {
    DBDataFormat dataFormat = null;

    try
    {
      DBSQLRequestDS request = null; //@P0A
      try
      {
        int requestedORS = DBSQLRequestDS.ORS_BITMAP_RETURN_DATA
            + DBSQLRequestDS.ORS_BITMAP_DATA_FORMAT              
            + DBSQLRequestDS.ORS_BITMAP_SQLCA; 
        if (extendedMetaData_) { 
          requestedORS = requestedORS + DBSQLRequestDS.ORS_BITMAP_EXTENDED_COLUMN_DESCRIPTORS; 
        }
        request = DBDSPool.getDBSQLRequestDS ( //@P0C
                                             DBSQLRequestDS.FUNCTIONID_OPEN_DESCRIBE,
                                             id_, requestedORS,                                  // @E1A
                                             0);   //@isol  Note:  ORS_BITMAP_CURSOR_ATTRIBUTES not needed here since it is specified by callers before this point

      request.setOpenAttributes (openAttributes);
                //@F1 Even though multiple cases were required in AS400JDBCStatement, scrollable is 
                //@F1 always false when openDescribe is called, so the cursor is always forward-only 
                //@F1 and we need less cases here.  More cases would need to be added if the value 
                //@F1 of scrollable was not always false (between sensitive and insensitive 
                //@F1 resultSetType).  Right now, the value of false for scrollable always indicates 
                //@F1 forward-only (not scrollable).  If this changed, instead of a boolean, we'd need  
                //@F1 an int with sensitive/insensitive/forward-only passed in as the choices to this 
                //@F1 method.
                //@F1 If we are pre-V5R2, send what we always have.
                /* @KBD if (connection_.getVRM() < JDUtilities.vrm520)              //@F1A
                {
      request.setScrollableCursorFlag (scrollable ? 1 : 0);
                }
                else
                {   //else check value of "cursor sensitivity" property
                    if (!scrollable)
                    {
                        // If the property is set to the default value, send same value we always have.
                        String cursorSensitivity = connection_.getProperties().getString(JDProperties.CURSOR_SENSITIVITY);    //@F1A
                        if (cursorSensitivity.equalsIgnoreCase(JDProperties.CURSOR_SENSITIVITY_ASENSITIVE))                   //@F1A
                            request.setScrollableCursorFlag (DBSQLRequestDS.CURSOR_NOT_SCROLLABLE_ASENSITIVE);                //@F1A
                        else if (cursorSensitivity.equalsIgnoreCase(JDProperties.CURSOR_SENSITIVITY_INSENSITIVE))             //@F1A
                            request.setScrollableCursorFlag (DBSQLRequestDS.CURSOR_NOT_SCROLLABLE_INSENSITIVE);               //@F1A
                        else                                                                                                  //@F1A
                            request.setScrollableCursorFlag (DBSQLRequestDS.CURSOR_NOT_SCROLLABLE_SENSITIVE);                 //@F1A
                    }                                                                                                         //@F1A
                    else //add more cases if this method starts being called with scrollable not always equal to false        //@F1A
                        request.setScrollableCursorFlag (DBSQLRequestDS.CURSOR_SCROLLABLE_ASENSITIVE); //@F1A
                }
                */
                String cursorSensitivity = connection_.getProperties().getString(JDProperties.CURSOR_SENSITIVITY);    //@F8A
                if(connection_.getVRM() <= JDUtilities.vrm520)    //@KBA
                {
                    //@KBA IF pre-V5R3 always set to NOT SCROLLABLE ASENSITIVE
                    //if(resultSetType == ResultSet.TYPE_FORWARD_ONLY)    //@KBA
                        request.setScrollableCursorFlag (DBSQLRequestDS.CURSOR_NOT_SCROLLABLE_ASENSITIVE);    //@KBA        Option 0
                    //else    //@KBA
                    //    request.setScrollableCursorFlag (DBSQLRequestDS.CURSOR_SCROLLABLE_ASENSITIVE);    //@KBA        Option 1
                }    //@KBA
                else {
                    /* @H1A Use common routine to determine scrollability */  
                   request.setScrollableCursorFlag (
                       AS400JDBCResultSet.getDBSQLRequestDSCursorType(cursorSensitivity, resultSetType, ResultSet.CONCUR_READ_ONLY));
                }    //@KBA

      if (openReply != null) { openReply.returnToPool(); openReply =null; }  
      openReply = connection_.sendAndReceive (request, id_); //@P0C

      int errorClass = openReply.getErrorClass();
      int returnCode = openReply.getReturnCode();

      if (errorClass != 0)
        JDError.throwSQLException (this, connection_, id_, errorClass, returnCode);

      processConcurrencyOverride(openAttributes, openReply);                            // @E1A @E4C
      dataFormat = openReply.getDataFormat ();
      setExtendedColumnDescriptorsFromReply(  openReply);   //@P6A
      // @550A  NOTE:  openDescribe() currently is only called for a result set returned from a CallableStatement
      // if that were to change, this method needs to be modified so that it correctly indicates to the data format
      // when the data is from a stored procedure result set.
      dataFormat.setCSRSData(true);		// @550A  indicate to the data format that this data is associated with a stored procedure result set
      }
      finally
      {
        if (request != null) { request.returnToPool(); request = null; } 
        // if (openReply != null) { openReply.returnToPool(); openReply = null; }
      }
    }
    catch (DBDataStreamException e)
    {
      JDError.throwSQLException (JDError.EXC_INTERNAL, e);                            // @E5C
    }

    closed_ = false;

    if (JDTrace.isTraceOn())
      JDTrace.logOpen (this, null);                   // @J33a

    return dataFormat;
  }



// @E1A @E4C
/**
Processes a potential cursor concurrency override from a reply.
It is assumed that the reply contains a SQLCA.  This means
that you have to include the SQLCA bit as part of the ORS
bitmap in the request.

@param openAttributes   The requested open attributes.
@param reply            The reply.
**/
  void processConcurrencyOverride(int openAttributes, DBBaseReplyDS reply)
  throws DBDataStreamException
  {
    // If the system overrides our open attributes, reflect that fact.
    DBReplySQLCA sqlca = reply.getSQLCA ();
    switch (sqlca.getWarn5())
    {
      case (byte)0xF1:  // EBCDIC'1' Read only
      case (byte)0xF2:  // EBCDIC'2' Read and deleteable
        concurrency_ = ResultSet.CONCUR_READ_ONLY;
        break;
      case (byte)0xF4:  // EBCDIC'4' Read, deleteable, and updateable
        concurrency_ = ResultSet.CONCUR_UPDATABLE;
        break;
        /* @E7D - We should not do an override if this is an old system.
        default:    // Old system (without override indication)
            switch(openAttributes) {
            case OPEN_READONLY_:
                concurrency_ = ResultSet.CONCUR_READ_ONLY;
                break;
            case OPEN_ALL_:
            default:
                concurrency_ = ResultSet.CONCUR_UPDATABLE;
                break;
            }
            break;
        */
    }
  }


  
//@cur new method
/**
Processes a cursor attributes from a reply.

@param reply            The reply.
**/
 void processCursorAttributes(DBBaseReplyDS reply)
 throws SQLException
 {
     holdable_ = reply.getCursorAttributeHoldable();
     scrollable_ = reply.getCursorAttributeScrollable();
     updatable_ = reply.getCursorAttributeUpdatable();
     sensitive_ = reply.getCursorAttributeSensitive();
     isolationLevel_ = reply.getCursorIsolationLevel(); //@isol
 }


 //@cur new method
 /**
 Returns the cursor attribute holdable.
 @return The server attribute holdable. (1 is holdable, 0 is not holdable)
 **/
 public int getCursorAttributeHoldable()
 {
     return holdable_;     
 }
 
 //@cur new method
 /**
 Returns the cursor attribute Scrollable.
 @return The server attribute Scrollable. (1 is Scrollable, 0 is not Scrollable)
 **/
 public int getCursorAttributeScrollable()
 {
     return scrollable_;
 }
 
 //@cur new method
 /**
 Returns the cursor attribute Updatable.
 @return The server attribute Updatable. (1 is Updatable, 0 is not Updatable)
 **/
 public int getCursorAttributeUpdatable()
 {
     return updatable_;
 }
 
 //@cur new method
 /**
 Returns the cursor attribute Sensitive.
 @return The server attribute Sensitive. (1 is Sensitive, 0 is not Sensitive)
 **/
 public int getCursorAttributeSensitive()
 {
     return sensitive_;
 }
 
 
 //@isol new method
 /**
 Returns the cursor isolation level.
 @return The cursor isolation level.
 **/
 public int getCursorIsolationLevel()
 {
     return isolationLevel_;
 }

 /**
  * Sets whether extended metadata should be requested when the cursor is
  * opened. 
  * @param extendedMetaData
  */
void setExtendedMetaData(boolean extendedMetaData) {
  extendedMetaData_=extendedMetaData; 
}
 
/**
Set the cursor name.

@param  name    The cursor name.
**/
  void setName (String name)
  {
    name_ = name;

    if (JDTrace.isTraceOn())
      JDTrace.logProperty (this, "setName", "Name", name_);
  }



/**
Sets the state of the cursor.  This is useful when a
request implicitly opens or closes the cursor.

@param  closed  true for close, or false for open
**/
  void setState (boolean closed)
  {
    closed_ = closed;

    if (JDTrace.isTraceOn())
    {
      if (closed_)
        JDTrace.logClose (this);
      else
        JDTrace.logOpen (this, null);                       // @J33a
    }
  }

/**
 * Returns the extended column descriptors
 */
  DBExtendedColumnDescriptors getExtendedColumnDescriptors() {
    return extendedColumnDescriptors_;
  }
  
/**
Returns the cursor name.

@return     the cursor name.
**/
  public String toString ()
  {
    return name_;
  }



}

