/*
 ========== licence begin GPL
    Copyright (C) 2002-2003 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end
 */


package  com.sap.dbtech.jdbc;

import  java.sql.*;


import  com.sap.dbtech.util.MessageKey;
import  com.sap.dbtech.util.MessageTranslator;
import  com.sap.dbtech.jdbc.exceptions.SQLExceptionSapDB;

/**
 * The representation of a savepoint, which is a point within
 * the current transaction that can be referenced from the
 * <code>Connection.rollback</code> method. When a transaction
 * is rolled back to a savepoint all changes made after that
 * savepoint are undone.
 * <p>
 * Savepoints can be either named or unnamed. Unnamed savepoints
 * are identified by an ID generated by the underlying data source.
 * @since 1.4 JDBC 3.0
 *
 */
public class SavepointSapDB
        implements Savepoint {
    private int SavepointId = 0;
    private String SavepointName = null;
    private boolean released = false;
    private static int savepointIndex = 0;
    private static final String savepointPrefix_C = "JDBC_SP_";
    private com.sap.dbtech.jdbc.InternalStatementSapDB internalStmt = null;
   
    /**
     * Creates a new un-named savepoint.
     * @since 1.4 JDBC 3.0
     *
     */
    public SavepointSapDB (ConnectionSapDB conn) throws SQLException{
        this.SavepointId = SavepointSapDB.nextSavepointID();
        this.internalStmt = new  com.sap.dbtech.jdbc.InternalStatementSapDB (conn, com.sap.dbtech.vsp001.SqlMode.Oracle_C);
    }

    /**
     * Creates a new named savepoint.
     * @param aSavepointName name of savepoint
     * @since 1.4 JDBC 3.0
     *
     */
    public SavepointSapDB (String aSavepointName, ConnectionSapDB conn) throws SQLException{
        this.SavepointName = aSavepointName;
        this.internalStmt = new  com.sap.dbtech.jdbc.InternalStatementSapDB (conn, com.sap.dbtech.vsp001.SqlMode.Oracle_C);
    }

    /**
     * Retrieves the generated ID for the savepoint that this
     * <code>Savepoint</code> object represents.
     * @return the numeric ID of this savepoint
     * @throws java.sql.SQLException if this is a named savepoint
     * @since 1.4 JDBC 3.0
     *
     */
    public int getSavepointId () throws java.sql.SQLException {
        if (this.SavepointId == 0) {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_NAMED_SAVEPOINT));
        } else {
            return  this.SavepointId;
        }
    }

    /**
     * Retrieves the name of the savepoint that this <code>Savepoint</code>
     * object represents.
     * @return the name of this savepoint
     * @throws java.sql.SQLException if this is an un-named savepoint
     * @since 1.4 JDBC 3.0
     *
     */
    public String getSavepointName () throws java.sql.SQLException {
        if (this.SavepointName == null) {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_UNNAMED_SAVEPOINT));
        } else {
            return  this.SavepointName;
        }
    }

    /**
     * Generates a new unique SavepointId
     * @return a new unique SavepointId
     */
    final static synchronized int nextSavepointID () {
        savepointIndex++;
        return  savepointIndex;
    }

    /**
     * Generates a savepointname from the current savepointid for internal use, because
     * the database kernel cannot handle un-named savepoints.
     * @return the generated savepointname
     */
    final String getInternalSavepointName () {
        if (this.SavepointName == null)
            return  savepointPrefix_C + String.valueOf(this.SavepointId);
        else
            return  this.SavepointName;
    }

    /**
     * Makes a "rollback to savepoint" command and sends it to the database kernel
     * @param stmt a valid <code>java.sql.statement</code>
     * @throws java.sql.SQLException if a database access error occurs
     */
    private void rollbackSavepoint () throws java.sql.SQLException {
        this.internalStmt.execute("ROLLBACK TO SAVEPOINT " + this.getInternalSavepointName());
    }

    /**
     * Sets a savepoint in state released
     */
    private void releaseSavepoint () throws SQLException {
        this.released = true;
        if(this.internalStmt.getConnectionSapDB().isReleaseSavePointSupported()) {
        	try {
        		this.internalStmt.execute("RELEASE SAVEPOINT " + this.getInternalSavepointName());
        	} catch(SQLException sqlEx) {
        		if(sqlEx.getErrorCode() == -3005) {
        			this.internalStmt.getConnectionSapDB().setReleaseSavePointSupported(false);
        		} else {
        			throw sqlEx;
        		}
        	}
        }
        		   
    }

    /**
     * Checks if a savepoint is in state released
     * @return true - if savepoint is released and false - if checkpoint isn`t released
     */
    private boolean isrelease () {
        return  this.released;
    }
    /**
   * Creates an unnamed savepoint in the current transaction and
   * returns the new <code>Savepoint</code> object that represents it.
   * @param conn a ConnectionSapDB object
   * @return the new <code>Savepoint</code> object
   * @see Savepoint
   * @since 1.4
   *
   */
    public static java.sql.Savepoint setSavepoint (ConnectionSapDB conn) throws java.sql.SQLException {
        SavepointSapDB sp = new SavepointSapDB(conn);
        sp.internalStmt.execute("SAVEPOINT " + sp.getInternalSavepointName());
        return  (java.sql.Savepoint)sp;
    }

    /**
   * Creates an unnamed savepoint in the current transaction and
   * returns the new <code>Savepoint</code> object that represents it.
   * @param SavepointName a savepointname
   * @param conn a ConnectionSapDB object
   * @return the new <code>Savepoint</code> object
   * @see Savepoint
   * @since 1.4
   *
   */
    public static java.sql.Savepoint setSavepoint (String SavepointName, ConnectionSapDB conn) throws java.sql.SQLException {
        SavepointSapDB sp = new SavepointSapDB(SavepointName, conn);
        sp.internalStmt.execute("SAVEPOINT " + sp.getInternalSavepointName());
        return  (java.sql.Savepoint)sp;
    }

    /**
     * Undoes all changes made after the given <code>Savepoint</code> object
     * was set.
     * <P>
     * This method should be used only when auto-commit has been disabled.
     *
     * @param savepoint the <code>Savepoint</code> object to roll back to
     * @exception SQLException if a database access error occurs,
     *            the <code>Savepoint</code> object is no longer valid,
     *            or this <code>Connection</code> object is currently in
     *            auto-commit mode
     * @see Savepoint
     * @see #rollback
     * @since 1.4
     */
    public static void rollback (java.sql.Savepoint savepoint) throws java.sql.SQLException {
        SavepointSapDB sp;
        try {
            sp = (SavepointSapDB)savepoint;
        } catch (ClassCastException classCastEx) {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_NO_SAVEPOINTSAPDB,
                                                                    savepoint.getClass()));
        }
        if (sp.isrelease()) {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_SAVEPOINT_RELEASED));
        }
        sp.rollbackSavepoint();
    }

    /**
     * Removes the given <code>Savepoint</code> object from the current
     * transaction. Any reference to the savepoint after it have been removed
     * will cause an <code>SQLException</code> to be thrown.
     *
     * @param savepoint the <code>Savepoint</code> object to be removed
     * @exception SQLException if a database access error occurs or
     *            the given <code>Savepoint</code> object is not a valid
     *            savepoint in the current transaction
     * @since 1.4
     */
    public static void releaseSavepoint (java.sql.Savepoint savepoint) throws java.sql.SQLException {
        SavepointSapDB sp;
        try {
            sp = (SavepointSapDB)savepoint;
        } catch (java.lang.ClassCastException _exc) {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_NO_SAVEPOINTSAPDB,
                                                                    savepoint.getClass()));
        }
        if (sp.isrelease())
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_SAVEPOINT_RELEASED));
        sp.releaseSavepoint();
    }
}



