/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

package org.teiid.jdbc;

import java.sql.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.transaction.xa.Xid;

import org.teiid.client.DQP;
import org.teiid.client.plan.Annotation;
import org.teiid.client.plan.PlanNode;
import org.teiid.client.util.ResultsFuture;
import org.teiid.client.xa.XATransactionException;
import org.teiid.client.xa.XidImpl;
import org.teiid.core.types.ArrayImpl;
import org.teiid.core.util.PropertiesUtils;
import org.teiid.core.util.SqlUtil;
import org.teiid.net.CommunicationException;
import org.teiid.net.ConnectionException;
import org.teiid.net.ServerConnection;
import org.teiid.net.TeiidURL;
import org.teiid.net.socket.SocketServerConnection;

/**
 * Teiid's Connection implementation.
 */
public class ConnectionImpl extends WrapperImpl implements TeiidConnection {
	private static final int MAX_OPEN_STATEMENTS = PropertiesUtils.getIntProperty(System.getProperties(), "org.teiid.maxOpenStatements", 1000); //$NON-NLS-1$

	private static Logger logger = Logger.getLogger("org.teiid.jdbc"); //$NON-NLS-1$

	public static final int DEFAULT_ISOLATION = Connection.TRANSACTION_READ_COMMITTED;
	
	// constant value giving product name
    private final static String SERVER_NAME = "Teiid Server"; //$NON-NLS-1$
    private final static String EMBEDDED_NAME = "Teiid Embedded"; //$NON-NLS-1$

    // Unique request ID generator
    private long requestIDGenerator;

    // url used to create the connection
    private String url;
 
    // properties object containing the connection properties.
    protected Properties propInfo;

    // status of connection object
    private boolean closed = false;
    // determines if a statement executed should be immediately committed.
    private boolean autoCommitFlag = true;
    private boolean inLocalTxn;

    // collection of all open statements on this connection
    private Collection<StatementImpl> statements = Collections.newSetFromMap(new ConcurrentHashMap<StatementImpl, Boolean>());
    // cached DatabaseMetadata
    private DatabaseMetaDataImpl dbmm;

   //Xid for participating in TXN
    private XidImpl transactionXid;
            
    //  Flag to represent if the connection state needs to be readOnly, default value false.
    private boolean readOnly = false;
    
    private DQP dqp;
    protected ServerConnection serverConn;
    private int transactionIsolation = DEFAULT_ISOLATION;
    
    //  the last query plan description
    private PlanNode currentPlanDescription;
    // the last query debug log
    private String debugLog;
    // the last query annotations
    private Collection<Annotation> annotations;
    private Properties connectionProps;
    private Properties payload;
        
    public ConnectionImpl(ServerConnection serverConn, Properties info, String url) { 
    	this.connectionProps = info;
    	this.serverConn = serverConn;
        this.url = url;
        this.dqp = serverConn.getService(DQP.class);
        
        if (logger.isLoggable(Level.FINE)) {
	        logger.fine(JDBCPlugin.Util.getString("MMConnection.Session_success")); //$NON-NLS-1$
	        logConnectionProperties(url, info);
        }
        
        setExecutionProperties(info);
    }
    
    boolean isInLocalTxn() {
		return inLocalTxn;
	}
    
	private void setExecutionProperties(Properties info) {
		this.propInfo = new Properties();
        
        String defaultFetchSize = info.getProperty(ExecutionProperties.PROP_FETCH_SIZE);
        if (defaultFetchSize != null) {
        	propInfo.put(ExecutionProperties.PROP_FETCH_SIZE, defaultFetchSize);
        } else {
        	propInfo.put(ExecutionProperties.PROP_FETCH_SIZE, String.valueOf(BaseDataSource.DEFAULT_FETCH_SIZE)); 
        }

        String partialResultsMode = info.getProperty(ExecutionProperties.PROP_PARTIAL_RESULTS_MODE);
        if (partialResultsMode != null) {
        	propInfo.put(ExecutionProperties.PROP_PARTIAL_RESULTS_MODE, partialResultsMode);
        } else {
        	propInfo.put(ExecutionProperties.PROP_PARTIAL_RESULTS_MODE, BaseDataSource.DEFAULT_PARTIAL_RESULTS_MODE);
        }
        
        String resultSetCacheMode = info.getProperty(ExecutionProperties.RESULT_SET_CACHE_MODE);
        if (resultSetCacheMode != null) {
        	propInfo.put(ExecutionProperties.RESULT_SET_CACHE_MODE, resultSetCacheMode);
        } else {
        	propInfo.put(ExecutionProperties.RESULT_SET_CACHE_MODE, BaseDataSource.DEFAULT_RESULT_SET_CACHE_MODE);
        }

        String ansiQuotes = info.getProperty(ExecutionProperties.ANSI_QUOTED_IDENTIFIERS);
        if (ansiQuotes != null) {
        	propInfo.put(ExecutionProperties.ANSI_QUOTED_IDENTIFIERS, ansiQuotes);
        } else {
        	propInfo.put(ExecutionProperties.ANSI_QUOTED_IDENTIFIERS, Boolean.TRUE.toString());
        }
                                
        for (String key : info.stringPropertyNames()) {
        	String actualKey = JDBCURL.EXECUTION_PROPERTIES.get(key);
        	if (actualKey != null) {
        		propInfo.setProperty(actualKey, info.getProperty(key));
        	}
		}
	}
    
    public Collection<Annotation> getAnnotations() {
		return annotations;
	}
    
    public void setAnnotations(Collection<Annotation> annotations) {
		this.annotations = annotations;
	}
    
    public String getDebugLog() {
		return debugLog;
	}
    
    public void setDebugLog(String debugLog) {
		this.debugLog = debugLog;
	}
    
    public PlanNode getCurrentPlanDescription() {
		return currentPlanDescription;
	}
    
    public void setCurrentPlanDescription(PlanNode currentPlanDescription) {
		this.currentPlanDescription = currentPlanDescription;
	}
    
    protected Properties getExecutionProperties() {
        return this.propInfo;
    }
    
    public void setExecutionProperty(String key, String value) {
    	JDBCURL.addNormalizedProperty(key, value, getExecutionProperties());
    }
    
    public String getExecutionProperty(String key) {
    	return this.getExecutionProperties().getProperty(JDBCURL.getValidKey(key));
    }
    
    DQP getDQP() {
    	return this.dqp;
    }
    
    /**
     * Remove password & trusted token and log all other properties
     * @param connUrl - URL used to connect to server
     * @param info - properties object supplied
     */
    private void logConnectionProperties(String connUrl, Properties info) {
        StringBuffer modifiedUrl = new StringBuffer();

        // If we have valid URL
        if (connUrl != null) {
	        // We need wipe out the password here, before we write to the log
	        int startIndex = connUrl.indexOf("password="); //$NON-NLS-1$
	        if (startIndex != -1) {
	            modifiedUrl.append(connUrl.substring(0, startIndex));
	            modifiedUrl.append("password=***"); //$NON-NLS-1$
	            int endIndex = connUrl.indexOf(";", startIndex+9); //$NON-NLS-1$
	            if (endIndex != -1) {
	                modifiedUrl.append(";").append(connUrl.substring(endIndex)); //$NON-NLS-1$
	            }
	        }
	        logger.fine("Connection Url="+modifiedUrl); //$NON-NLS-1$
        }
        
        // Now clone the properties object and remove password and trusted token
        if (info != null) {
            Enumeration enumeration = info.keys();
            while (enumeration.hasMoreElements()) {
                String key = (String)enumeration.nextElement();
                Object anObj = info.get(key);
                // Log each property except for password and token.
                if (!TeiidURL.CONNECTION.PASSWORD.equalsIgnoreCase(key)) { 
                    logger.fine(key+"="+anObj); //$NON-NLS-1$
                }
            }
        }                              
    }
        
    String getUrl() {
        return this.url;
    }
    
    /**
     * Connection identifier of this connection 
     * @return identifier
     * @throws SQLException 
     */
    public String getConnectionId() {
    	return this.serverConn.getLogonResult().getSessionID();
    }
    
    /**
     * Generate the next unique requestID for matching up requests with responses.
     * These IDs should be unique only in the context of a ServerConnection instance.
     * @return Request ID
     */
    protected synchronized long nextRequestID() {
        return requestIDGenerator++;
    }

    public void clearWarnings() throws SQLException {
        // do nothing
    }

    public void close() throws SQLException {
    	Throwable firstException = null;

    	if(closed) {
            return;
        }

        try {
            // close any statements that were created on this connection
        	try {
        		closeStatements();
        	} catch (SQLException se) {
        		firstException = se;
        	} finally {
        		this.serverConn.close();
                if ( firstException != null )
                	throw (SQLException)firstException;
        	}
        } catch (SQLException se) {
            throw TeiidSQLException.create(se, JDBCPlugin.Util.getString("MMConnection.Err_connection_close", se.getMessage())); //$NON-NLS-1$
        } finally {
            logger.fine(JDBCPlugin.Util.getString("MMConnection.Connection_close_success")); //$NON-NLS-1$
            // set the status of the connection to closed
            closed = true;            
        }
    }

    /**
     * <p>
     * Close all the statements open on this connection
     * </p>
     * 
     * @throws SQLException
     *             server statement object could not be closed.
     */
    void closeStatements() throws SQLException {
        // Closing the statement will cause the
        // MMConnection.closeStatement() method to be called,
        // which will modify this.statements.  So, we do this iteration
        // in a separate safe copy of the list
        List<StatementImpl> statementsSafe = new ArrayList<StatementImpl>(this.statements);
        SQLException ex = null;
        for (StatementImpl statement : statementsSafe) {
            try {
            	statement.close();
            } catch (SQLException e) {
            	ex = e;
            }
        }
        if (ex != null) {
            throw TeiidSQLException.create(ex, JDBCPlugin.Util.getString("MMConnection.Err_closing_stmts")); //$NON-NLS-1$
        }
    }

    /**
     * Called by MMStatement to notify the connection that the
     * statement has been closed.
     * @param statement
     */
    void closeStatement(Statement statement) {
        this.statements.remove(statement);
    }

    /**
     * <p>This method makes any changes involved in a transaction permanent and releases
     * any locks held by the connection object.  This is only used when auto-commit
     * is set to false.</p>
     * @throws SQLException if the transaction had been rolled back or marked to roll back.
     */
    public void commit() throws SQLException {
        checkConnection();
        if (!autoCommitFlag) {
            try {
                directCommit();
            } finally {
                inLocalTxn = false; 
            }
        }
    }

    private void directCommit() throws SQLException {
    	if (inLocalTxn) {
	        try {
				ResultsFuture<?> future = this.dqp.commit();
				future.get();
			} catch (Exception e) {
				throw TeiidSQLException.create(e);
			}
	        logger.fine(JDBCPlugin.Util.getString("MMConnection.Commit_success")); //$NON-NLS-1$
    	}
    }

    void beginLocalTxnIfNeeded() throws SQLException {
        if (this.transactionXid != null || inLocalTxn || this.autoCommitFlag || isDisableLocalTxn()) {
        	return;
        }
        try {
        	try {
        		this.dqp.begin();
    		} catch (XATransactionException e) {
    			throw TeiidSQLException.create(e);
    		} 
            inLocalTxn = true;
        } finally {
            if (!inLocalTxn) {
                autoCommitFlag = true;
            }
        }
    }

	private boolean isDisableLocalTxn() {
		String prop = this.propInfo.getProperty(ExecutionProperties.DISABLE_LOCAL_TRANSACTIONS);
        return prop != null && Boolean.valueOf(prop);
	}
    
    public StatementImpl createStatement() throws SQLException {
        return createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    }

    /**
     * <p>Creates a Statement object that will produce ResultSet objects of the type
     * resultSetType and concurrency level resultSetConcurrency.</p>
     * @param intvalue indicating the ResultSet's type
     * @param intValue indicating the ResultSet's concurrency
     * @return Statement object.
     */
    public StatementImpl createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
    	return createStatement(resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT);
    }

    /** 
     * @param resultSetType
     * @throws TeiidSQLException
     * @since 4.3
     */
    private void validateResultSetType(int resultSetType) throws TeiidSQLException {
        if (resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE ) {
            String msg = JDBCPlugin.Util.getString("MMConnection.Scrollable_type_not_supported", "ResultSet.TYPE_SCROLL_SENSITIVE"); //$NON-NLS-1$ //$NON-NLS-2$
            throw new TeiidSQLException(msg);
        }
    }

    /** 
     * @param resultSetConcurrency
     * @throws TeiidSQLException
     * @since 4.3
     */
    private void validateResultSetConcurrency(int resultSetConcurrency) throws TeiidSQLException {
        if (resultSetConcurrency == ResultSet.CONCUR_UPDATABLE) {
            String msg = JDBCPlugin.Util.getString("MMConnection.Concurrency_type_not_supported", "ResultSet.CONCUR_UPDATABLE"); //$NON-NLS-1$ //$NON-NLS-2$
            throw new TeiidSQLException(msg);
        }
    }

    public boolean getAutoCommit() throws SQLException {
        //Check to see the connection is open
       checkConnection();
       return autoCommitFlag;
    }

    public String getCatalog() throws SQLException {
        //Check to see the connection is open
        checkConnection();
        //catalogs are not supported
        return this.serverConn.getLogonResult().getVdbName();
    }

    /**
     * <p>This method gets the ServerConnection object wrapped by this object.</p>
     * @return ServerConnection object
     */
    public ServerConnection getServerConnection() throws SQLException {
        //Check to see the connection is open
        checkConnection();
        return serverConn;
    }

    String getVDBName() throws SQLException {
        //Check to see the connection is open
        checkConnection();
        //get the virtual database name to which we are connected.

        return this.serverConn.getLogonResult().getVdbName();
    }
    
    public int getVDBVersion() throws SQLException {
    	checkConnection();
        return this.serverConn.getLogonResult().getVdbVersion();
    }

    /**
     * Get's the name of the user who got this connection.
     * @return Sring object giving the user name
     * @throws SQLException if the connection is closed
     */
    String getUserName() throws SQLException {
        checkConnection();

        return this.serverConn.getLogonResult().getUserName();
    }
    
    public DatabaseMetaDataImpl getMetaData() throws SQLException {
        //Check to see the connection is open
        checkConnection();
        
        if (dbmm == null) {
            dbmm = new DatabaseMetaDataImpl(this);
        }       
        return dbmm;
    }

    /**
     * Get the database name that this connection is representing 
     * @return String name of the database
     */
    public String getDatabaseName() {
    	if (this.serverConn instanceof SocketServerConnection) {
    		return SERVER_NAME;
    	}
    	return EMBEDDED_NAME;
    }
    
    /**
     * Retrieves the current holdability of ResultSet objects created using this Connection object.
     * @param holdability int indicating the holdability
     * @return int holdability
     * @throws SQLException
     */
    public int getHoldability() throws SQLException {
        return ResultSet.HOLD_CURSORS_OVER_COMMIT;
    }

    public int getTransactionIsolation() throws SQLException {
    	return this.transactionIsolation;
    }
    
    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
    	return Collections.emptyMap();
    }
    
    /**
     * <p>This method will return the first warning reported by calls on this connection,
     * or null if none exist.</p>
     * @return A SQLWarning object if there are any warnings.
     * @throws SQLException, should never occur
     */
    public SQLWarning getWarnings() throws SQLException {
        //Check to see the connection is open
        checkConnection();
        return null;  // we don't have any warnings
    }

    /**
     * <p>This method will return whether this connection is closed or not.</p>
     * @return booleanvalue indicating if the connection is closed
     * @throws SQLException, should never occur
     */
    public boolean isClosed() throws SQLException {
        return closed;
    }

    public boolean isReadOnly() throws SQLException {
    	checkConnection();
    	return readOnly; 
    }

    public String nativeSQL(String sql) throws SQLException {
        // return the string argument without any modifications.
        // escape syntaxes are directly supported in the server
        return sql;
    }

    /**
     * <p>Creates a CallableStatement object that contains sql and that will produce
     * ResultSet objects that are non-scrollable and non-updatable. A SQL stored
     * procedure call statement is handled by creating a CallableStatement for it.</p>
     * @param sql String(escape syntax) for invoking a stored procedure.
     * @return CallableStatement object that can be used to execute the storedProcedure
     * @throws SQLException if there is an error creating the callable statement object
     */
    public CallableStatementImpl prepareCall(String sql) throws SQLException {
    	//there is a problem setting the result set type to be non-scrollable
    	//See defect 17768
        return prepareCall(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
    }

    /**
     * <p>Creates a CallableStatement object that contains a sql and that will produce
     * ResultSet objects of the type resultSetType and with a concurrency level of
     * resultSetConcurrency. A SQL stored procedure call statement is handled by
     * creating a CallableStatement for it.</p>
     * @param sql String(escape syntax) for invoking a stored procedure.
     * @param intvalue indicating the ResultSet's type
     * @param intValue indicating the ResultSet's concurrency
     * @return CallableStatement object that can be used to execute the storedProcedure
     */
    public CallableStatementImpl prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
    	return prepareCall(sql, resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT);
    }

    /** 
     * @param sql
     * @throws TeiidSQLException
     * @since 4.3
     */
    private void validateSQL(String sql) throws TeiidSQLException {
        if (sql == null) {
            String msg = JDBCPlugin.Util.getString("MMConnection.SQL_cannot_be_null"); //$NON-NLS-1$
            throw new TeiidSQLException(msg);
        }
    }

    public PreparedStatementImpl prepareStatement(String sql) throws SQLException {
        return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    }

    public PreparedStatementImpl prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
    	return prepareStatement(sql, resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT);
    }

    public PreparedStatementImpl prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
        int resultSetHoldability, int autoGeneratedKeys) throws SQLException {
        //Check to see the connection is open
        checkConnection();
        
        validateResultSetType(resultSetType);
        validateResultSetConcurrency(resultSetConcurrency);
        validateSQL(sql);
        
        // add the statement object to the map
        PreparedStatementImpl newStatement = new PreparedStatementImpl(this, sql, resultSetType, resultSetConcurrency);
        newStatement.setAutoGeneratedKeys(autoGeneratedKeys == Statement.RETURN_GENERATED_KEYS);
        addStatement(newStatement);
        return newStatement;
    }
    
	public PreparedStatementImpl prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
	        int resultSetHoldability ) throws SQLException {
		return prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability, Statement.NO_GENERATED_KEYS);
    }

    public void rollback() throws SQLException {
        rollback(true);
    }
    
    /**
     * Rollback the current local transaction
     * @param startTxn
     * @throws SQLException
     */
    public void rollback(boolean startTxn) throws SQLException {

        //Check to see the connection is open
        checkConnection();
        if (!autoCommitFlag) {
        	if (this.transactionXid != null) {
                throw new TeiidSQLException(JDBCPlugin.Util.getString("MMStatement.In_XA_Transaction"));//$NON-NLS-1$ 
            }
            try {
            	if (this.inLocalTxn) {
            		this.inLocalTxn = false;
	            	try {
	            		ResultsFuture<?> future = this.dqp.rollback();
	            		future.get();
	        		} catch (Exception e) {
	        			throw TeiidSQLException.create(e);
	        		}
	                logger.fine(JDBCPlugin.Util.getString("MMConnection.Rollback_success")); //$NON-NLS-1$
            	}
            } finally {
                if (startTxn) {
                    this.inLocalTxn = false;
                }
                else {
                    this.autoCommitFlag = true;
                }
            }
        }
    }
    
	public ResultsFuture<?> submitSetAutoCommitTrue(boolean commit) throws SQLException {
    	//Check to see the connection is open
        checkConnection();

        if (this.autoCommitFlag) {
            return ResultsFuture.NULL_FUTURE;
        }
        
        this.autoCommitFlag = true;
        
        if (isDisableLocalTxn()) {
        	return ResultsFuture.NULL_FUTURE;
        }
    	
        try {
	        if (commit) {
	        	return dqp.commit();
	        } 
	        return dqp.rollback();
        } catch (XATransactionException e) {
        	throw TeiidSQLException.create(e);
        }
    }

    public void setAutoCommit(boolean autoCommit) throws SQLException {
        //Check to see the connection is open
        checkConnection();

        if (autoCommit == this.autoCommitFlag) {
            return;
        }
        
        if (autoCommit && this.transactionXid != null) {
            throw new TeiidSQLException(JDBCPlugin.Util.getString("MMStatement.In_XA_Transaction"));//$NON-NLS-1$ 
        }
        
        this.autoCommitFlag = autoCommit;

        if (autoCommit) {
            directCommit();   
        } else {
        	inLocalTxn = false;
        }
    }

    /**
     * <p>Teiid does not allow setting a catalog through a connection. This
     * method silently ignores the request as per the specification.</p>
     * @param The string values which sets the catalog name on the connection.
     * @throws SQLException This should never occur.
     */
    public void setCatalog(String catalog) throws SQLException {
        // do nothing, silently ignore the request
    }

    /**
     * @param A boolean value specifying whether the connection is readonly.
     * @throws throws SQLException.
     */
    public void setReadOnly(boolean readOnly) throws SQLException {
    	checkConnection();
        // During transaction do not allow to change this flag
        if (isInLocalTxn() || this.transactionXid != null) {
            throw new TeiidSQLException(JDBCPlugin.Util.getString("MMStatement.Invalid_During_Transaction", "setReadOnly(" + readOnly + ")"));//$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$
        }
        this.readOnly = readOnly;
    }

    /**
     * <p> This utility method checks if the jdbc connection is closed and
     * throws an exception if it is closed. </p>
     * @throws SQLException if the connection object is closed.
     */
    void checkConnection() throws TeiidSQLException{
        //Check to see the connection is closed and proceed if it is not
       if (closed) {
            throw new TeiidSQLException(JDBCPlugin.Util.getString("MMConnection.Cant_use_closed_connection")); //$NON-NLS-1$
        }
     }

    protected void commitTransaction(XidImpl arg0, boolean arg1) throws SQLException {
        checkConnection();
        transactionXid = null;
        this.autoCommitFlag = true;
        try {
        	ResultsFuture<?> future = this.dqp.commit(arg0, arg1);
        	future.get();
		} catch (Exception e) {
			throw TeiidSQLException.create(e);
		}
    }

    protected void endTransaction(XidImpl arg0, int arg1) throws SQLException {
        checkConnection();
        this.autoCommitFlag = true;
        try {
        	ResultsFuture<?> future = this.dqp.end(arg0, arg1);
        	future.get();
		} catch (Exception e) {
			throw TeiidSQLException.create(e);
		}
    }

    protected void forgetTransaction(XidImpl arg0) throws SQLException {
        checkConnection();
        try {
        	ResultsFuture<?> future = this.dqp.forget(arg0);
        	future.get();
		} catch (Exception e) {
			throw TeiidSQLException.create(e);
		}
    }

    protected int prepareTransaction(XidImpl arg0) throws SQLException  {
        checkConnection();
        transactionXid = null;
        try {
        	ResultsFuture<Integer> future = this.dqp.prepare(arg0);
        	return future.get();
		} catch (Exception e) {
			throw TeiidSQLException.create(e);
		}
    }

    protected Xid[] recoverTransaction(int arg0) throws SQLException  {
        checkConnection();
        try {
			ResultsFuture<Xid[]> future = this.dqp.recover(arg0);
			return future.get();
		} catch (Exception e) {
			throw TeiidSQLException.create(e);
		}
    }

    protected void rollbackTransaction(XidImpl arg0) throws SQLException {
        checkConnection();
        transactionXid = null;
        this.autoCommitFlag = true;
        try {
        	ResultsFuture<?> future = this.dqp.rollback(arg0);
        	future.get();
		} catch (Exception e) {
			throw TeiidSQLException.create(e);
		}
    }

    protected void startTransaction(XidImpl arg0, int arg1, int timeout) throws SQLException {
        checkConnection();
        try {
        	ResultsFuture<?> future = this.dqp.start(arg0, arg1, timeout);
        	future.get();
		} catch (Exception e) {
			throw TeiidSQLException.create(e);
		}
        transactionXid = arg0;
        this.autoCommitFlag = false;
    }

    protected XidImpl getTransactionXid() {
        return transactionXid;
    }
    
	public boolean isValid(int timeout) throws SQLException {
		return this.getServerConnection().isOpen(timeout * 1000);
	}
	
	public void recycleConnection(boolean selectNewInstance) {
		this.payload = null;
        try {
        	//close all open statements
        	this.closeStatements();
        } catch (SQLException e) {
            logger.log(Level.WARNING, JDBCPlugin.Util.getString("MMXAConnection.rolling_back_error"), e); //$NON-NLS-1$
        }
        try {
            //rollback if still in a transaction
            if (!this.getAutoCommit()) {
                logger.warning(JDBCPlugin.Util.getString("MMXAConnection.rolling_back")); //$NON-NLS-1$
                
                if (this.getTransactionXid() == null) {
                    this.rollback(false);
                } else {
                	this.rollbackTransaction(getTransactionXid());
                }
            }
        } catch (SQLException e) {
        	logger.log(Level.WARNING, JDBCPlugin.Util.getString("MMXAConnection.rolling_back_error"), e); //$NON-NLS-1$
        }
        
        if (selectNewInstance) {
        	this.serverConn.cleanUp();
        }
	}
	
	public boolean isSameProcess(ConnectionImpl conn) throws CommunicationException {
		return this.serverConn.isSameInstance(conn.serverConn);
	}
	
	public void setClientInfo(Properties properties)
		throws SQLClientInfoException {
	}

	public void setClientInfo(String name, String value)
		throws SQLClientInfoException {
	}
	
	public Properties getClientInfo() throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public String getClientInfo(String name) throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public Array createArrayOf(String typeName, Object[] elements)
			throws SQLException {
		return new ArrayImpl(elements);
	}

	public Blob createBlob() throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public Clob createClob() throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public NClob createNClob() throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public SQLXML createSQLXML() throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public StatementImpl createStatement(int resultSetType,
			int resultSetConcurrency, int resultSetHoldability)
			throws SQLException {
        //Check to see the connection is open
        checkConnection();

        validateResultSetType(resultSetType);
        validateResultSetConcurrency(resultSetConcurrency);
        //TODO: implement close cursors at commit

        // add the statement object to the map
        StatementImpl newStatement = new StatementImpl(this, resultSetType, resultSetConcurrency);
        addStatement(newStatement);

        return newStatement;
	}

	private void addStatement(StatementImpl newStatement) throws SQLException {
		if (statements.size() > MAX_OPEN_STATEMENTS) {
			this.close();
			throw new TeiidSQLException(JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID20036, MAX_OPEN_STATEMENTS));
		}
		statements.add(newStatement);
	}

	public Struct createStruct(String typeName, Object[] attributes)
			throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public CallableStatementImpl prepareCall(String sql, int resultSetType,
			int resultSetConcurrency, int resultSetHoldability)
			throws SQLException {
        //Check to see the connection is open
        checkConnection();
        
        validateResultSetType(resultSetType);
        validateResultSetConcurrency(resultSetConcurrency);
        validateSQL(sql);
        //TODO: implement close cursors at commit
        
        // add the statement object to the map
        CallableStatementImpl newStatement = new CallableStatementImpl(this, sql, resultSetType, resultSetConcurrency);
        addStatement(newStatement);
        return newStatement;
	}

	public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
			throws SQLException {
		return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT, Statement.RETURN_GENERATED_KEYS);
	}

	public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
			throws SQLException {
		return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT, Statement.RETURN_GENERATED_KEYS);
	}

	public PreparedStatement prepareStatement(String sql, String[] columnNames)
			throws SQLException {
		return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT, Statement.RETURN_GENERATED_KEYS);
	}

	public void releaseSavepoint(Savepoint savepoint) throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public void rollback(Savepoint savepoint) throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public void setHoldability(int holdability) throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public Savepoint setSavepoint() throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public Savepoint setSavepoint(String name) throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public void setTransactionIsolation(int level) throws SQLException {
		this.transactionIsolation = level;
	}

	public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}
	
	Object setPassword(Object newPassword) {
		if (newPassword != null) {
			return this.connectionProps.put(TeiidURL.CONNECTION.PASSWORD, newPassword);
		} 
		return this.connectionProps.remove(TeiidURL.CONNECTION.PASSWORD);
	}
	
	String getPassword() {
		Object result = this.connectionProps.get(TeiidURL.CONNECTION.PASSWORD);
		if (result == null) {
			return null;
		}
		return result.toString();
	}
	
	@Override
	public void changeUser(String userName, String newPassword)
			throws SQLException {
		//TODO: recycleConnection();
		Object oldName = null;
		Object oldPassword = null;
		if (userName != null) {
			oldName = this.connectionProps.put(TeiidURL.CONNECTION.USER_NAME, userName);
		} else {
			oldName = this.connectionProps.remove(TeiidURL.CONNECTION.USER_NAME);
		}
		oldPassword = setPassword(newPassword);
		boolean success = false;
		try {
			this.serverConn.authenticate();
			success = true;
		} catch (ConnectionException e) {
			throw TeiidSQLException.create(e);
		} catch (CommunicationException e) {
			throw TeiidSQLException.create(e);
		} finally {
			if (!success) {
				if (oldName != null) {
					this.connectionProps.put(TeiidURL.CONNECTION.USER_NAME, oldName);
				} else {
					this.connectionProps.remove(TeiidURL.CONNECTION.USER_NAME);
				}
				setPassword(oldPassword);
			}
		}
	}

	public void abort(Executor executor) throws SQLException {
		if (closed) {
			return;
		}
		//TODO: ensure that threads are released.  In theory they will be since close effectively cancels current executions
		close();
	}

	public int getNetworkTimeout() throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public String getSchema() throws SQLException {
		return null;
	}

	/**
	 * @see query timeouts and the synchronousTtl setting if using socket connections
	 */
	public void setNetworkTimeout(Executor executor, int milliseconds)
			throws SQLException {
		throw SqlUtil.createFeatureNotSupportedException();
	}

	public void setSchema(String schema) throws SQLException {
		
	}
	
	public Properties getPayload() {
		return payload;
	}
	
	public void setPayload(Properties payload) {
		this.payload = payload;
	}
	
	public Properties getConnectionProps() {
		return connectionProps;
	}
	
	void setTransactionXid(XidImpl transactionXid) {
		this.transactionXid = transactionXid;
	}
	
	public void setInLocalTxn(boolean inLocalTxn) {
		this.inLocalTxn = inLocalTxn;
	}
	
}
