/*

   Derby - Class org.apache.derby.impl.jdbc.TransactionResourceImpl

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to you under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */

package org.apache.derby.impl.jdbc;

import org.apache.derby.jdbc.InternalDriver;

import org.apache.derby.iapi.services.context.Context;
import org.apache.derby.iapi.services.context.ContextService;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.iapi.services.monitor.Monitor;
import org.apache.derby.shared.common.sanity.SanityManager;

import org.apache.derby.iapi.db.Database;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
import org.apache.derby.iapi.error.ExceptionSeverity;

import org.apache.derby.iapi.reference.Attribute;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.reference.Property;
import org.apache.derby.iapi.util.StringUtil;
import org.apache.derby.iapi.util.IdUtil;
import org.apache.derby.iapi.util.InterruptStatus;

import java.util.Properties;
import java.sql.SQLException;

/** 
 *	An instance of a TransactionResourceImpl is a bundle of things that
 *	connects a connection to the database - it is the transaction "context" in
 *	a generic sense.  It is also the object of synchronization used by the
 *	connection object to make sure only one thread is accessing the underlying
 *	transaction and context.
 *
 *  <P>TransactionResourceImpl not only serves as a transaction "context", it
 *	also takes care of: <OL>
 *	<LI>context management: the pushing and popping of the context manager in
 *		and out of the global context service</LI>
 *	<LI>transaction demarcation: all calls to commit/abort/prepare/close a
 *		transaction must route thru the transaction resource.
 *	<LI>error handling</LI>
 *	</OL>
 *
 *  <P>The only connection that have access to the TransactionResource is the
 *  root connection, all other nested connections (called proxyConnection)
 *  accesses the TransactionResource via the root connection.  The root
 *  connection may be a plain EmbedConnection, or a DetachableConnection (in
 *  case of a XATransaction).  A nested connection must be a ProxyConnection.
 *  A proxyConnection is not detachable and can itself be a XA connection -
 *  although an XATransaction may start nested local (proxy) connections.
 *
 *	<P> this is an example of how all the objects in this package relate to each
 *		other.  In this example, the connection is nested 3 deep.  
 *		DetachableConnection.  
 *	<P><PRE>
 *
 *      lcc  cm   database  jdbcDriver
 *       ^    ^    ^         ^ 
 *       |    |    |         |
 *      |======================|
 *      | TransactionResource  |
 *      |======================|
 *             ^  |
 *             |  |
 *             |  |      |---------------rootConnection----------|
 *             |  |      |                                       |
 *             |  |      |- rootConnection-|                     |
 *             |  |      |                 |                     |
 *             |  V      V                 |                     |
 *|========================|      |=================|      |=================|
 *|    EmbedConnection     |      | EmbedConnection |      | EmbedConnection |
 *|                        |&lt;-----|                 |&lt;-----|                 |
 *| (DetachableConnection) |      | ProxyConnection |      | ProxyConnection |
 *|========================|      |=================|      |=================|
 *   ^                 | ^             ^                        ^
 *   |                 | |             |                        |
 *   ---rootConnection-- |             |                        |
 *                       |             |                        |
 *                       |             |                        |
 * |======================|  |======================|  |======================|
 * | ConnectionChild |  | ConnectionChild |  | ConnectionChild |
 * |                      |  |                      |  |                      |
 * |  (EmbedStatement)    |  |  (EmbedResultSet)    |  |  (...)               |
 * |======================|  |======================|  |======================|
 *
 * </PRE>
 * <P>A plain local connection <B>must</B> be attached (doubly linked with) to a
 * TransactionResource at all times.  A detachable connection can be without a
 * TransactionResource, and a TransactionResource for an XATransaction
 * (called  XATransactionResource) can be without a connection.
 *
 *
 */
public final class TransactionResourceImpl
{
	/*
	** instance variables set up in the constructor.
	*/
	// conn is only present if TR is attached to a connection
	protected ContextManager cm;
	protected ContextService csf;
	protected String username;

	private String dbname;
	private InternalDriver driver;
	private String url;
	private String drdaID;

	// set these up after constructor, called by EmbedConnection
	protected Database database;
	protected LanguageConnectionContext lcc;

	/**
	 * create a brand new connection for a brand new transaction
	 */
	TransactionResourceImpl(
							InternalDriver driver, 
							String url,
							Properties info) throws SQLException 
	{
		this.driver = driver;
		csf = driver.getContextServiceFactory();
		dbname = InternalDriver.getDatabaseName(url, info);
		this.url = url;

		// the driver manager will push a user name
		// into the properties if its getConnection(url, string, string)
		// interface is used.  Thus, we look there first.
		// Default to APP.
		username = IdUtil.getUserNameFromURLProps(info);

		drdaID = info.getProperty(Attribute.DRDAID_ATTR, null);

		// make a new context manager for this TransactionResource

		// note that the Database API requires that the 
		// getCurrentContextManager call return the context manager
		// associated with this session.  The JDBC driver assumes
		// responsibility (for now) for tracking and installing
		// this context manager for the thread, each time a database
		// call is made.
		cm = csf.newContextManager();
	}

	/**
	 * Called only in EmbedConnection construtor.
	 * The Local Connection sets up the database in its constructor and sets it
	 * here.
	 */
	void setDatabase(Database db)
	{
		if (SanityManager.DEBUG)
			SanityManager.ASSERT(database == null, 
				"setting database when it is not null"); 

		database = db;
	}

	/*
	 * Called only in EmbedConnection constructor.  Create a new transaction
	 * by creating a lcc.
	 *
	 * The arguments are not used by this object, it is used by
	 * XATransactionResoruceImpl.  Put them here so that there is only one
	 * routine to start a local connection.
	 */
	void startTransaction() throws StandardException, SQLException
	{
		// setting up local connection
		lcc = database.setupConnection(cm, username, drdaID, dbname);
	}

	/**
	 * Return instance variables to EmbedConnection.  RESOLVE: given time, we
	 * should perhaps stop giving out reference to these things but instead use
	 * the transaction resource itself.
	 */
	InternalDriver getDriver() {
		return driver;
	}
	ContextService getCsf() {
		return  csf;
	}

	/**
	 * need to be public because it is in the XATransactionResource interface
	 */
	ContextManager getContextManager() {
		return  cm;
	}

	LanguageConnectionContext getLcc() {
		return  lcc;
	}
	String getDBName() {
		return  dbname;
	}
	String getUrl() {
		return  url;
	}
	Database getDatabase() {
		return  database;
	}

	StandardException shutdownDatabaseException() {
		StandardException se = StandardException.newException(SQLState.SHUTDOWN_DATABASE, getDBName());
		se.setReport(StandardException.REPORT_NEVER);
		return se;
	}

	/**
	 * local transaction demarcation - note that global or xa transaction
	 * cannot commit thru the connection, they can only commit thru the
	 * XAResource, which uses the xa_commit or xa_rollback interface as a 
	 * safeguard. 
	 */
	void commit() throws StandardException
	{
		lcc.userCommit();
	}		

	void rollback() throws StandardException
	{
		// lcc may be null if this is called in close.
		if (lcc != null)
			lcc.userRollback();
	}

	/*
	 * context management
	 */

	/**
	 * An error happens in the constructor, pop the context.
	 */
	void clearContextInError()
	{
		csf.resetCurrentContextManager(cm);
		cm = null;
	}

	/**
	 * Resolve: probably superfluous
	 */
	void clearLcc()
	{
		lcc = null;
	}

	final void setupContextStack()
	{
		if (SanityManager.DEBUG) {
			SanityManager.ASSERT(cm != null, "setting up null context manager stack");
		}

			csf.setCurrentContextManager(cm);
	}

	final void restoreContextStack() {

		if ((csf == null) || (cm == null))
			return;
		csf.resetCurrentContextManager(cm);
	}

	/*
	 * exception handling
	 */

	/**
	 * clean up the error and wrap the real exception in some SQLException.
	 */
	final SQLException handleException(Throwable thrownException,
									   boolean autoCommit,	
									   boolean rollbackOnAutoCommit)
			throws SQLException 
	{
		try {
			if (SanityManager.DEBUG)
				SanityManager.ASSERT(thrownException != null);

			/*
				just pass SQL exceptions right back. We assume that JDBC driver
				code has cleaned up sufficiently. Not passing them through would mean
				that all cleanupOnError methods would require knowledge of Utils.
			 */
			if (thrownException instanceof SQLException) {

                InterruptStatus.restoreIntrFlagIfSeen();
				return (SQLException) thrownException;

			} 

			boolean checkForShutdown = false;
			if (thrownException instanceof StandardException)
			{
				StandardException se = (StandardException) thrownException;
				int severity = se.getSeverity();
				if (severity <= ExceptionSeverity.STATEMENT_SEVERITY)
				{
					/*
					** If autocommit is on, then do a rollback
					** to release locks if requested.  We did a stmt 
					** rollback in the cleanupOnError above, but we still
					** may hold locks from the stmt.
					*/
					if (autoCommit && rollbackOnAutoCommit)
					{
						se.setSeverity(ExceptionSeverity.TRANSACTION_SEVERITY);
					}
				} else if (SQLState.CONN_INTERRUPT.equals(se.getMessageId())) {
					// an interrupt closed the connection.
					checkForShutdown = true;
				}
			}
			// if cm is null, we don't have a connection context left,
			// it was already removed.  all that's left to cleanup is
			// JDBC objects.
			if (cm!=null) {
			    //diagActive should be passed to cleanupOnError
			    //only if a session is active, Login errors are a special case where
			    // the database is active but the session is not.
				boolean sessionActive = (database != null) && database.isActive() && 
					!isLoginException(thrownException);
				boolean isShutdown = cleanupOnError(thrownException, sessionActive);
				if (checkForShutdown && isShutdown) {
					// Change the error message to be a known shutdown.
					thrownException = shutdownDatabaseException();
				}
			}

            InterruptStatus.restoreIntrFlagIfSeen();

			return wrapInSQLException(thrownException);

		} catch (Throwable t) {

            if (cm != null) { // something to let us cleanup?
                cm.cleanupOnError(t, database != null ? isActive() : false);
			}

            InterruptStatus.restoreIntrFlagIfSeen();

			/*
			   We'd rather throw the Throwable,
			   but then javac complains...
			   We assume if we are in this degenerate
			   case that it is actually a java exception
			 */
			throw wrapInSQLException(t);
			//throw t;
		}

	}

    /**
     * Determine if the exception thrown is a login exception.
     * Needed for DERBY-5427 fix to prevent inappropriate thread dumps
     * and javacores. This exception is special because it is 
     * SESSION_SEVERITY and database.isActive() is true, but the 
     * session hasn't started yet,so it is not an actual crash and 
     * should not report extended diagnostics.
     * 
     * @param thrownException
     * @return true if this is a login failure exception
     */
    private boolean isLoginException(Throwable thrownException) {
       if ((thrownException instanceof StandardException) &&
           ((StandardException) thrownException).getSQLState().equals(SQLState.LOGIN_FAILED)) {
           return true;
       }
       return false;
    }
    
    /**
     * Wrap a <code>Throwable</code> in an <code>SQLException</code>.
     *
     * @param thrownException a <code>Throwable</code>
     * @return <code>thrownException</code>, if it is an
     * <code>SQLException</code>; otherwise, an <code>SQLException</code> which
     * wraps <code>thrownException</code>
     */
	public static SQLException wrapInSQLException(Throwable thrownException) {

		if (thrownException == null)
			return null;

		if (thrownException instanceof SQLException) {
            // Server side JDBC can end up with a SQLException in the nested
            // stack. Return the exception with no wrapper.
            return (SQLException) thrownException;
		}

        if (thrownException instanceof StandardException) {

			StandardException se = (StandardException) thrownException;

            if (SQLState.CONN_INTERRUPT.equals(se.getSQLState())) {
                Thread.currentThread().interrupt();
            }

            if (se.getCause() == null) {
                // se is a single, unchained exception. Just convert it to an
                // SQLException.
                return Util.generateCsSQLException(se);
            }

            // se contains a non-empty exception chain. We want to put all of
            // the exceptions (including Java exceptions) in the next-exception
            // chain. Therefore, call wrapInSQLException() recursively to
            // convert the cause chain into a chain of SQLExceptions.
            return Util.seeNextException(se.getMessageId(),
                        wrapInSQLException(se.getCause()), se.getCause(),
                        se.getArguments());
        }

        // thrownException is a Java exception
        return Util.javaException(thrownException);
	}

	/*
	 * TransactionResource methods
	 */

	String getUserName() {
		return  username;
	}

    /**
     * clean up error and print it to derby.log if diagActive is true
     * @param e the error we want to clean up
     * @param diagActive
     *        true if extended diagnostics should be considered, 
     *        false not interested of extended diagnostic information
     * @return true if the context manager is shutdown, false otherwise.
     */
    boolean cleanupOnError(Throwable e, boolean diagActive)
	{
		if (SanityManager.DEBUG)
			SanityManager.ASSERT(cm != null, "cannot cleanup on error with null context manager");

        //DERBY-4856 thread dump
        return cm.cleanupOnError(e, diagActive);
	}

	boolean isIdle()
	{
		// If no lcc, there is no transaction.
		return (lcc == null || lcc.getTransactionExecute().isIdle());
	}


	/*
	 * class specific methods
	 */


	/* 
	 * is the underlaying database still active?
	 */
	boolean isActive()
	{
		// database is null at connection open time
		return (driver.isActive() && ((database == null) || database.isActive()));
	}

}


