/* * Copyright (C) 2007 SQL Explorer Development Team http://sourceforge.net/projects/eclipsesql * * This program 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package net.sourceforge.sqlexplorer.dbproduct; import java.sql.SQLException; import java.util.LinkedList; import net.sourceforge.sqlexplorer.ExplorerException; import net.sourceforge.sqlexplorer.connections.ConnectionsView; import net.sourceforge.sqlexplorer.plugin.SQLExplorerPlugin; /** * The SessionTreeNode represents one active database session. * * @modified Davy Vanherbergen */ public class Session { /* * A QueuedTask is run once the current connection becomes idle */ private interface QueuedTask { public void run() throws SQLException; } // User definition we connect as (will be null after we've been closed) private User user; // Connection to the database private SQLConnection connection; // Last selected catalog private String lastCatalog; // Whether the connection is currently "grabbed" by calling code private boolean connectionInUse; // Whether to keep the connection and NOT release it back to the pool private boolean keepConnection; // Whether we auto-commit private boolean autoCommit; // Whether we commit on close (only really applies if autoCommit==false) private boolean commitOnClose; // List of tasks to execute when the current connection is freed up private LinkedList<QueuedTask> queuedTasks = new LinkedList<QueuedTask>(); /** * Constructor; ties this Session to a User configuration but does not allocate a SQL connection until required. * * @param user */ /* package */Session(User user) throws SQLException { this.user = user; autoCommit = user.isAutoCommit(); commitOnClose = user.isCommitOnClose(); } /** * Returns true if the session is valid * * @return */ public synchronized boolean isValidSession() { return user != null; } /** * Called internally to set the connection (including setting it to null) * * @param newConnection */ protected void internalSetConnection(SQLConnection newConnection) throws SQLException { if (newConnection != null && connection != newConnection && connection != null) { throw new IllegalStateException("Cannot change connection on the fly!"); } if (connection != null) { connection.setSession(null); } connection = newConnection; if (connection != null) { connection.setSession(this); if (lastCatalog != null) { connection.setCatalog(lastCatalog); } connection.setAutoCommit(autoCommit); connection.setCommitOnClose(commitOnClose); } } /** * Returns the current connection, if there is one. Intended only for use by derived classes. * * @return */ protected SQLConnection getConnection() { return connection; } /** * Grabs a connection; note that releaseConnection MUST be called to return the connection for use by other code. * * @return * @throws ExplorerException */ public synchronized SQLConnection grabConnection() throws SQLException { if (user == null) { throw new IllegalStateException("Session invalid (closed)"); } if (connectionInUse) { throw new IllegalStateException("Cannot grab a new connection - already in use"); } if (connection != null) { if (connection.getConnection() == null || connection.getConnection().isClosed()) { internalSetConnection(null); } } // If we don't have one yet, get one from the pool if (connection == null) { internalSetConnection(user.getConnection()); } if (connection != null) { connectionInUse = true; } ConnectionsView connectionsView = SQLExplorerPlugin.getDefault().getConnectionsView(); if (connectionsView != null) { connectionsView.refresh(); } return connection; } /** * Releases the connection; if the connection does NOT have auto-commit, this session will hang on to it for next * time, otherwise it is returned to the pool * * @param toRelease */ public synchronized void releaseConnection(SQLConnection toRelease) { if (!connectionInUse) { throw new IllegalStateException("Cannot release connection - not inuse"); } if (connection != toRelease) { // User will be null if we've closed if (user == null) { return; } throw new IllegalArgumentException("Attempt to release the wrong connection"); } // Run any queued tasks try { while (!queuedTasks.isEmpty()) { QueuedTask task = queuedTasks.removeFirst(); task.run(); } } catch (SQLException e) { SQLExplorerPlugin.error("Failed running queued task", e); } connectionInUse = false; try { // Update the connection to the auto-commit and commit-on-close status connection.setAutoCommit(autoCommit); connection.setCommitOnClose(commitOnClose); // If it's not auto-commit, then we have to keep the connection if (!autoCommit || keepConnection) { return; } } catch (SQLException e) { SQLExplorerPlugin.error("Cannot commit", e); } // Give it back into the pool try { user.releaseConnection(connection); internalSetConnection(null); } catch (SQLException e) { SQLExplorerPlugin.error("Cannot release connection", e); } ConnectionsView connectionsView = SQLExplorerPlugin.getDefault().getConnectionsView(); if (connectionsView != null) { connectionsView.refresh(); } } /** * Returns whether the connection is in use or not * * @return */ public synchronized boolean isConnectionInUse() { return connectionInUse; } /** * Returns trie if auto-commit is enabled * * @return */ public boolean isAutoCommit() { return autoCommit; } public synchronized void setAutoCommit(boolean autoCommit) throws SQLException { boolean enabling = !this.autoCommit && autoCommit; this.autoCommit = autoCommit; // If we're turning it on, get rid of any existing connection back to the pool if (enabling) { // If there's a connection but its not in use, then release it if (connection != null && !connectionInUse) { try { user.releaseConnection(connection); internalSetConnection(null); ConnectionsView connectionsView = SQLExplorerPlugin.getDefault().getConnectionsView(); if (connectionsView != null) { connectionsView.refresh(); } } catch (SQLException e) { SQLExplorerPlugin.error("Cannot release connection", e); } } } } public boolean isCommitOnClose() { return commitOnClose; } public void setCommitOnClose(boolean commitOnClose) { this.commitOnClose = commitOnClose; } /** * Queues a task to be completed at the end of the current * * @param task * @throws SQLException */ protected void queueTask(QueuedTask task) throws ExplorerException { // If we have a connection and it's not in use, then just run it if (connection != null && !connectionInUse) { try { task.run(); } catch (SQLException e) { throw new ExplorerException(e); } return; } // Queue it queuedTasks.add(task); // If the connection's not in use, grab one and release; the grab // ensures we have a connection, and the release flushes the queue if (!connectionInUse) { try { grabConnection(); } catch (SQLException e) { throw new ExplorerException(e); } finally { releaseConnection(connection); } } } /** * Closes the session and returns any connection to the pool. Note that isConnectionInUse() must return false or * close() will throw an exception. * * @throws ExplorerException */ public synchronized void close() { if (connectionInUse) { user.disposeConnection(connection); try { internalSetConnection(null); } catch (SQLException e) { SQLExplorerPlugin.error(e); } } // Disconnection from the user if (connection != null) { try { user.releaseConnection(connection); } catch (SQLException e) { SQLExplorerPlugin.error(e); } try { internalSetConnection(null); } catch (SQLException e) { SQLExplorerPlugin.error(e); } } user.releaseSession(this); user = null; } /** * Forces the connection (if there is one) to be closed, rolling back any open transactions * */ public synchronized void disposeConnection() { if (connectionInUse) { throw new IllegalAccessError("Cannot close session while connection is still in use!"); } if (connection != null) { SQLConnection connection = this.connection; try { if (!connection.getAutoCommit()) { connection.rollback(); } } catch (SQLException e) { SQLExplorerPlugin.error(e); } try { user.disposeConnection(connection); internalSetConnection(null); } catch (SQLException e) { SQLExplorerPlugin.error(e); } } } /** * Commits the connection; this will queue if the connection is currently in use * */ public synchronized void commit() throws ExplorerException { queueTask(new QueuedTask() { @Override public void run() throws SQLException { connection.commit(); } }); } /** * Rolls back the connection; this will queue if the connection is currently in use * */ public synchronized void rollback() throws ExplorerException { queueTask(new QueuedTask() { @Override public void run() throws SQLException { connection.rollback(); } }); } /** * Changes the catalog of the connection; this will queue if the connection is currently in use * */ public synchronized void setCatalog(final String catalog) throws ExplorerException { lastCatalog = catalog; if (catalog != null && connection != null) { queueTask(new QueuedTask() { @Override public void run() throws SQLException { connection.setCatalog(catalog); } }); } } public User getUser() { return user; } /* package */void setUser(User user) { this.user = user; } public AliasManager getAliases() { return SQLExplorerPlugin.getDefault().getAliasManager(); } @Override public String toString() { return user != null ? user.toString() : "(disconnected)"; } public DatabaseProduct getDatabaseProduct() { if (getUser() == null) { return null; } return getUser().getAlias().getDriver().getDatabaseProduct(); } /** * @return the keepConnection */ public boolean isKeepConnection() { return keepConnection; } /** * @param keepConnection the keepConnection to set */ public void setKeepConnection(boolean keepConnection) { this.keepConnection = keepConnection; } }