ca.sqlpower.swingui.query.SQLQueryUIComponents.java Source code

Java tutorial

Introduction

Here is the source code for ca.sqlpower.swingui.query.SQLQueryUIComponents.java

Source

/*
 * Copyright (c) 2009, SQL Power Group Inc.
 *
 * This file is part of SQL Power Library.
 *
 * SQL Power Library 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 3 of the License, or
 * (at your option) any later version.
 *
 * SQL Power 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */

package ca.sqlpower.swingui.query;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.beans.PropertyChangeSupport;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Document;
import javax.swing.undo.UndoManager;

import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rtextarea.RTextScrollPane;

import ca.sqlpower.sql.CachedRowSet;
import ca.sqlpower.sql.DataSourceCollection;
import ca.sqlpower.sql.DatabaseListChangeEvent;
import ca.sqlpower.sql.DatabaseListChangeListener;
import ca.sqlpower.sql.JDBCDataSource;
import ca.sqlpower.sql.RowSetChangeEvent;
import ca.sqlpower.sql.RowSetChangeListener;
import ca.sqlpower.sql.SPDataSource;
import ca.sqlpower.sqlobject.SQLDatabase;
import ca.sqlpower.sqlobject.SQLDatabaseMapping;
import ca.sqlpower.sqlobject.SQLObjectException;
import ca.sqlpower.swingui.SPSUtils;
import ca.sqlpower.swingui.SPSwingWorker;
import ca.sqlpower.swingui.SwingWorkerRegistry;
import ca.sqlpower.swingui.db.DatabaseConnectionManager;
import ca.sqlpower.swingui.event.TaskTerminationEvent;
import ca.sqlpower.swingui.event.TaskTerminationListener;
import ca.sqlpower.swingui.table.FancyExportableJTable;
import ca.sqlpower.swingui.table.ResultSetTableModel;
import ca.sqlpower.validation.swingui.StatusComponent;

import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.layout.FormLayout;

/**
 * A "bag of components" that are already wired together to cooperate as a GUI environment
 * for writing, debugging, and executing a SQL query. There are two approaches to using
 * this class:
 * <ol>
 *  <li>Use the provided factory method, which creates an instance of the class
 *      and arranges all the components in the usual way and returns a "ready
 *      to use" Swing component that behaves as an interactive SQL query tool.
 *      The factory method is {@link #createQueryPanel(SwingWorkerRegistry, DataSourceCollection)}.
 *  <li>Use the constructor to create an instance of this class, then use
 *      the public getter methods to retrieve all of the components you want
 *      in your UI, and arrange them yourself in any layout and combination
 *      that you require.
 * </ol>
 */
public class SQLQueryUIComponents {

    private static final Logger logger = Logger.getLogger(SQLQueryUIComponents.class);

    /**
     * The entry value in the input map that will map a key press to our
     * "Execute" action.
     */
    private static final String EXECUTE_QUERY_ACTION = "Execute Query";

    /**
     * The entry value in the input map that will map a key press to our
     * undo action on the sql edit text area.
     */

    private static final Object UNDO_SQL_EDIT = "Undo SQL Edit";

    /**
     * The entry value in the input map that will map a key press to our
     * redo action on the sql edit text area.
     */
    private static final Object REDO_SQL_EDIT = "Redo SQL Edit";

    /**
     * A listener for item selection on a combo box containing {@link SPDataSource}s.
     * This will create a new entry in the connection map to store a live connection
     * for the selected database.
     */
    private class DatabaseItemListener implements ItemListener {

        public void itemStateChanged(ItemEvent e) {
            if (e.getStateChange() != ItemEvent.SELECTED) {
                updateStatus();
                return;
            }
            JDBCDataSource ds = (JDBCDataSource) e.getItem();
            SQLDatabase db = databaseMapping.getDatabase(ds);
            try {
                addConnection(db);
            } catch (SQLObjectException e1) {
                logTextArea.append(createErrorStringMessage(e1));
                logTextArea.setCaretPosition(logTextArea.getDocument().getLength());
            }
        }

    }

    /**
    * This TextArea stores an exception Message if it ever Happens
     */
    private final JTextArea errorTextArea = new JTextArea();

    /**
     * This action will save the text in a document to a user selected file.
     * The text will either append to the file or overwrite the file's contents. 
     */
    private class SaveDocumentAction extends AbstractAction {

        private final Document doc;
        private final Component parent;
        private final boolean append;

        public SaveDocumentAction(Document doc, Component parent, boolean append) {
            super("Save As...");
            this.doc = doc;
            this.parent = parent;
            this.append = append;
        }

        public void actionPerformed(ActionEvent e) {
            JFileChooser chooser = new JFileChooser();
            chooser.addChoosableFileFilter(SPSUtils.LOG_FILE_FILTER);
            chooser.addChoosableFileFilter(SPSUtils.TEXT_FILE_FILTER);
            chooser.setFileFilter(SPSUtils.LOG_FILE_FILTER);
            int retval = chooser.showSaveDialog(parent);
            if (retval == JFileChooser.APPROVE_OPTION) {
                if (logger.isDebugEnabled()) {
                    try {
                        logger.debug("Log has length " + doc.getLength() + " and text "
                                + doc.getText(0, doc.getLength()) + " when writing to file.");
                    } catch (BadLocationException e1) {
                        throw new RuntimeException(e1);
                    }
                }
                logger.debug("Are we appending? " + append);

                String filePath = chooser.getSelectedFile().getAbsolutePath();
                if (!chooser.getSelectedFile().getName().contains(".")) {
                    if (chooser.getFileFilter() == SPSUtils.TEXT_FILE_FILTER) {
                        filePath = filePath + ".txt";
                    } else {
                        filePath = filePath + ".log";
                    }
                }
                if (append) {
                    FileAppender appender = null;
                    Logger logAppender = null;
                    try {
                        appender = new FileAppender(new PatternLayout("%m\n"), filePath);
                        logAppender = Logger.getLogger("SQLQueryUIComponents Log Appender");
                        logAppender.addAppender(appender);
                        logAppender.info(doc.getText(0, doc.getLength()));
                    } catch (Exception e1) {
                        throw new RuntimeException(e1);
                    } finally {
                        if (logAppender != null && appender != null) {
                            logAppender.removeAppender(appender);
                        }
                    }
                } else {
                    try {
                        FileWriter writer = new FileWriter(filePath);
                        writer.write(doc.getText(0, doc.getLength()));
                        writer.flush();
                        writer.close();
                    } catch (Exception e1) {
                        throw new RuntimeException(e1);
                    }
                }
            }
        }

    }

    /**
     * This mouse listener will be attached to the log in the results area to give users
     * an easy way to save the log to a file.
     */
    private final MouseListener logPopUpMouseListener = new MouseListener() {

        private JCheckBoxMenuItem checkBoxMenuItem = new JCheckBoxMenuItem("Append", true);

        public void mouseReleased(MouseEvent e) {
            logger.debug("Mouse released on log pop-up");
            showPopup(e);
        }

        public void mousePressed(MouseEvent e) {
            showPopup(e);
        }

        public void mouseExited(MouseEvent e) {
            showPopup(e);
        }

        public void mouseEntered(MouseEvent e) {
            showPopup(e);
        }

        public void mouseClicked(MouseEvent e) {
            showPopup(e);
        }

        private void showPopup(MouseEvent e) {
            if (e.isPopupTrigger()) {
                JPopupMenu logPopupMenu = new JPopupMenu();
                logPopupMenu.add(new JMenuItem(new SaveDocumentAction(logTextArea.getDocument(), logTextArea,
                        checkBoxMenuItem.isSelected())));
                logPopupMenu.add(checkBoxMenuItem);
                logPopupMenu.show(e.getComponent(), e.getX(), e.getY());
                logPopupMenu.setVisible(true);
            }
        }
    };

    /**
     * This will execute the sql statement in the sql text area. The
     * SQL statement used in execution will be stored with this swing
     * worker. If a different SQL statement is to be executed later
     * a new worker should be created.
     */
    private class ExecuteSQLWorker extends SPSwingWorker {

        private List<CachedRowSet> resultSets = new ArrayList<CachedRowSet>();
        private List<Integer> rowsAffected = new ArrayList<Integer>();
        private final SQLDatabase db;
        private long startExecutionTime;
        private final StatementExecutor stmtExecutor;
        private final StatementExecutorListener executorListener = new StatementExecutorListener() {
            public void queryStopped() {
                stopButton.setEnabled(false);
            }

            public void queryStarted() {
                stopButton.setEnabled(true);
            }
        };

        /**
         * Constructs a new ExecuteSQLWorker that will use the given SQL
         * statement as the string to execute on.
         * 
         * @param registry
         *            The registry to notify when this task begins and ends.
         * @param stmtExecutor
         *            The statement executor that actually executes the query.
         *            This object will be considered this worker's
         *            {@link #getResponsibleObject() responsible object}.
         */
        public ExecuteSQLWorker(SwingWorkerRegistry registry, StatementExecutor stmtExecutor) {
            super(registry, stmtExecutor);
            this.stmtExecutor = stmtExecutor;
            if (stmtExecutor.getStatement().equals("")) {
                logger.debug("Empty String");
                // if the string is empty there will be no execute so we need to reset the Panel from here.
                firstResultPanel.removeAll();
                firstResultPanel.revalidate();
            }

            this.stmtExecutor.addStatementExecutorListener(executorListener);

            db = databaseMapping.getDatabase((JDBCDataSource) databaseComboBox.getSelectedItem());

            try {
                rowLimitSpinner.commitEdit();
            } catch (ParseException e1) {
                // If the spinner can't parse it's current value set it to it's previous
                // value to keep it an actual number.
                rowLimitSpinner.setValue(rowLimitSpinner.getValue());
            }

            updateStatus();

            setJobSize(null);
            setProgress(0);
            if (db != null) {
                setMessage(Messages.getString("SQLQuery.workerMessage", db.getName()));
            } else {
                setMessage(Messages.getString("SQLQuery.queryingNullDB"));
            }
        }

        @Override
        public void cleanup() throws Exception {
            try {
                long finishExecutionTime = System.currentTimeMillis();
                DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG);
                logTextArea.append("Executed at " + formatter.format(new Date(startExecutionTime)) + ", took "
                        + (finishExecutionTime - startExecutionTime) + " milliseconds\n");
                Throwable e = getDoStuffException();
                if (e != null) {
                    String errorMessage = createErrorStringMessage(e);
                    logTextArea.append(errorMessage + "\n");
                    logger.error(e.getStackTrace());
                    clearResultTables(false);
                    for (Map.Entry<JTable, JScrollPane> entry : resultJTableScrollPanes.entrySet()) {
                        JPanel panel = new JPanel(new BorderLayout());
                        panel.add(entry.getKey().getTableHeader(), BorderLayout.NORTH);
                        panel.add(new JTextArea(Messages.getString("SQLQuery.queryFailedSeeLog", e.getMessage())));
                        entry.getValue().getViewport().setView(panel);
                    }
                    return;
                }

                if (queuedSQLStatement == null) {
                    createResultSetTables(resultSets, stmtExecutor);
                }

                resultSets.clear();
                for (Integer i : rowsAffected) {
                    logTextArea.append(Messages.getString("SQLQuery.rowsAffected", i.toString()));
                    logTextArea.append("\n");
                }
            } finally {
                logTextArea.append("\n");
                updateStatus();
                logTextArea.setCaretPosition(logTextArea.getDocument().getLength());
                this.stmtExecutor.removeStatementExecutorListener(executorListener);
            }
        }

        @Override
        public void doStuff() throws Exception {
            try {
                startExecutionTime = System.currentTimeMillis();
                logger.debug("Starting execute action of \"" + stmtExecutor.getStatement() + "\".");
                if (db == null) {
                    return;
                }
                if (stmtExecutor.getStatement().trim().length() == 0) {
                    return;
                }
                if (conMap.get(db) == null || conMap.get(db).getConnection() == null
                        || conMap.get(db).getConnection().isClosed()) {
                    addConnection(db);
                }

                updateStatus();

                logger.debug("Executing statement " + stmtExecutor.getStatement());
                boolean sqlResult = stmtExecutor.executeStatement();
                logger.debug("Finished execution");
                boolean hasNext = true;

                while (hasNext) {
                    if (sqlResult) {
                        ResultSet rs = stmtExecutor.getResultSet();
                        CachedRowSet rowSet;
                        logger.debug("Populating cached row set");
                        if (rs instanceof CachedRowSet) {
                            rowSet = (CachedRowSet) rs;
                        } else {
                            rowSet = new CachedRowSet();
                            rowSet.populate(rs);
                        }
                        logger.debug("Result set row count is " + rowSet.size());
                        resultSets.add(rowSet);
                        rowsAffected.add(new Integer(rowSet.size()));
                        rs.close();
                    } else {
                        rowsAffected.add(new Integer(stmtExecutor.getUpdateCount()));
                        logger.debug("Update count is : " + stmtExecutor.getUpdateCount());
                    }
                    sqlResult = stmtExecutor.getMoreResults();
                    hasNext = !((sqlResult == false) && (stmtExecutor.getUpdateCount() == -1));
                }
                logger.debug("Finished Execute method");

            } finally {
                updateStatus();
            }
        }
    }

    private class DefaultStatementExecutor implements StatementExecutor {

        private final SQLDatabase db;
        private final String sqlString;
        private final int rowLimit;
        private final List<ResultSet> resultSets = new ArrayList<ResultSet>();
        private final List<Integer> updateCounts = new ArrayList<Integer>();
        private final List<StatementExecutorListener> executorListeners = new ArrayList<StatementExecutorListener>();
        private int resultPosition;

        /**
         * This list of listeners should be notified every time a CachedRowSet's
         * populate listener is updated. This should be done when streaming connections
         * is allowed in the Universal SQL Access tool.
         */
        private final List<RowSetChangeListener> rowSetChangeListeners = new ArrayList<RowSetChangeListener>();

        private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);

        public DefaultStatementExecutor(SQLDatabase db, String sqlString, int rowLimit) {
            this.rowLimit = rowLimit;
            this.sqlString = sqlString;
            this.db = db;
            resultPosition = 0;
        }

        public boolean isRunning() {
            boolean dbSelected = databaseComboBox.getSelectedItem() != null;
            ConnectionAndStatementBean selectedConnection;
            if (dbSelected) {
                JDBCDataSource selectedDS = (JDBCDataSource) databaseComboBox.getSelectedItem();
                selectedConnection = conMap.get(databaseMapping.getDatabase(selectedDS));
            } else {
                selectedConnection = null;
            }
            return dbSelected && selectedConnection != null && selectedConnection.getCurrentStmt() != null;
        }

        public boolean executeStatement() throws SQLException {
            resultPosition = 0;
            Connection con = null;
            Statement stmt = null;
            try {
                fireQueryExecutionStart();
                con = conMap.get(db).getConnection();
                stmt = con.createStatement();
                conMap.get(db).setCurrentStmt(stmt);

                stmt.setMaxRows(rowLimit);
                boolean initialResult = stmt.execute(sqlString);
                boolean sqlResult = initialResult;
                boolean hasNext = true;
                while (hasNext) {
                    if (sqlResult) {
                        CachedRowSet crs = new CachedRowSet();
                        crs.setMakeUppercase(false);
                        crs.populate(stmt.getResultSet());
                        resultSets.add(crs);
                    } else {
                        resultSets.add(null);
                    }
                    updateCounts.add(stmt.getUpdateCount());
                    sqlResult = stmt.getMoreResults();
                    hasNext = !((sqlResult == false) && (stmt.getUpdateCount() == -1));
                }
                return initialResult;
            } finally {
                if (stmt != null) {
                    try {
                        stmt.close();
                    } catch (SQLException ex) {
                        ex.printStackTrace();
                    }
                    conMap.get(db).setCurrentStmt(null);
                }
                fireQueryExecutionStop();
            }
        }

        public ResultSet getResultSet() {
            if (resultPosition >= resultSets.size()) {
                return null;
            }
            return resultSets.get(resultPosition);
        }

        public String getStatement() {
            return sqlString;
        }

        public int getUpdateCount() {
            if (resultPosition >= updateCounts.size()) {
                return -1;
            }
            return updateCounts.get(resultPosition);
        }

        public boolean getMoreResults() {
            resultPosition++;
            return resultPosition < resultSets.size() && resultSets.get(resultPosition) != null;
        }

        public void addRowSetChangeListener(RowSetChangeListener l) {
            rowSetChangeListeners.add(l);
        }

        public void removeRowSetChangeListener(RowSetChangeListener l) {
            rowSetChangeListeners.remove(l);
        }

        private void fireQueryExecutionStart() {
            for (StatementExecutorListener listener : this.executorListeners) {
                listener.queryStarted();
            }
        }

        private void fireQueryExecutionStop() {
            for (StatementExecutorListener listener : this.executorListeners) {
                listener.queryStopped();
            }
        }

        public void addStatementExecutorListener(StatementExecutorListener qcl) {
            this.executorListeners.add(qcl);
        }

        public void removeStatementExecutorListener(StatementExecutorListener qcl) {
            this.executorListeners.remove(qcl);
        }

    }

    /**
     * This is the Panel that holds the first result JTable, This is normally used when multiple queries
     * not enabled and you wish to return this panel instead of the tabbedResult panel.
     */
    private JPanel firstResultPanel;

    /**
     * The component whose nearest Window ancestor will own any dialogs
     * popped up by the query tool.
     */
    private final JComponent dialogOwner;

    /**
     * The worker that the execute action runs on to query the database and
     * create the result sets. If this is null there is no currently executing
     * worker.
     */
    private volatile ExecuteSQLWorker sqlExecuteWorker;

    private final TaskTerminationListener sqlExecuteTerminationListener = new TaskTerminationListener() {
        public void taskFinished(TaskTerminationEvent e) {
            executeQuery((StatementExecutor) null);
        }
    };

    /**
     * This stores the next SQL statement to be run when the currently executing worker
     * is running.
     */
    private StatementExecutor queuedSQLStatement;

    /**
     * The action for executing and displaying a user's query.
     */
    private final AbstractAction executeAction;

    /**
     * A mapping of data sources to live connections. These connections will be left
     * open until the panel's ancestor is closed. The connections are kept open so 
     * auto commit can be turned off and users can enter multiple queries before 
     * committing or rolling back. Additionally, it will allow switching of data
     * sources while keeping the commit or rollback execution sequence preserved.
     */
    private Map<SQLDatabase, ConnectionAndStatementBean> conMap;

    /**
     * The text area users can enter SQL queries to get data from the database.
     */
    private final RSyntaxTextArea queryArea;

    /**
     * A combo box of available connections the user have specified. The selected
     * one will have the query run on it when the user hits the execute button.
     */
    private final JComboBox databaseComboBox;

    /**
     * A JSpinner for the user to enter the row limit of a query.
     */
    private JSpinner rowLimitSpinner;

    /**
     * Toggles auto commit on an off for the selected connection.
     */
    private final JToggleButton autoCommitToggleButton;

    /**
     * Commits the changes made on the currently selected connection.
     */
    private final JButton commitButton;

    /**
     * Rolls back the changes made on the currently selected connection.
     */
    private final JButton rollbackButton;

    private JButton undoButton;
    private JButton redoButton;

    private JTabbedPane resultTabPane;
    private JTextArea logTextArea;
    private static final ImageIcon ICON = new ImageIcon(
            StatusComponent.class.getClassLoader().getResource("ca/sqlpower/swingui/query/search.png"));
    private ArrayList<JTable> resultJTables;

    /**
     * These {@link JScrollPane}s each contain one table in the resultJTables
     * list. They are stored to place an exception message in the scroll pane if
     * a query fails. The result JTables are mapped to the scroll panes they
     * are contained in.
     */
    private final Map<JTable, JScrollPane> resultJTableScrollPanes = new HashMap<JTable, JScrollPane>();

    /**
     * This maps the JTables to the SQL statement that created them.
     * Multiple tables can share the same string.
     */
    private final Map<JTable, String> tableToSQLMap;

    private SwingWorkerRegistry swRegistry;
    private final DataSourceCollection dsCollection;

    /**
     * The undo manager for the text area containing the SQL statement.
     */
    private UndoManager undoManager;

    private Action undoSQLStatementAction = new AbstractAction(Messages.getString("SQLQuery.undo")) {

        public void actionPerformed(ActionEvent arg0) {
            if (undoManager.canUndo()) {
                undoManager.undo();
            }

        }
    };

    private Action redoSQLStatementAction = new AbstractAction(Messages.getString("SQLQuery.redo")) {

        public void actionPerformed(ActionEvent arg0) {
            if (undoManager.canRedo()) {
                undoManager.redo();
            }

        }
    };

    /**
     * This recreates the database combo box when the list of databases changes.
     */
    private DatabaseListChangeListener dbListChangeListener = new DatabaseListChangeListener() {

        public void databaseAdded(DatabaseListChangeEvent e) {
            if (!(e.getDataSource() instanceof JDBCDataSource))
                return;
            logger.debug("dataBase added");
            databaseComboBox.addItem(e.getDataSource());
            databaseComboBox.revalidate();
        }

        public void databaseRemoved(DatabaseListChangeEvent e) {
            if (!(e.getDataSource() instanceof JDBCDataSource))
                return;
            logger.debug("dataBase removed");
            if (databaseComboBox.getSelectedItem() != null
                    && databaseComboBox.getSelectedItem().equals(e.getDataSource())) {
                databaseComboBox.setSelectedItem(null);
            }

            databaseComboBox.removeItem(e.getDataSource());
            databaseComboBox.revalidate();
        }

    };

    /**
     * This list keeps track of all previous queries executed to allow users to move through
     * their query history.
     */
    private final List<String> previousQueries;

    /**
     * The button to go back to old queries in the previousQueries list.
     */
    private final JButton prevQueryButton;

    /**
     * The button to go forward in the old query list.
     */
    private final JButton nextQueryButton;

    /**
     * This is the position in the list of previous queries where the user is if they
     * are moving between old and older queries.
     */
    private int prevQueryPosition;

    /**
     * This is the method that will close the dialog and remove any connections in the dialog
     */
    public void closingDialogOwner() {

        logger.debug("attempting to close");
        boolean commitedOrRollBacked = closeConMap();
        if (commitedOrRollBacked) {
            logger.debug("removing DatabaseListChangeListener and closing window");
            disconnectListeners();
            Window w = SwingUtilities.getWindowAncestor(dialogOwner);
            if (w != null) {
                w.setVisible(false);
            }
        }
    }

    /**
     * Closes all of the connections in the connection mapping. If the
     * connection being closed is not in an auto-commit state, a dialog will be
     * displayed with the option to roll back or commit the changes.
     * <p>
     * Any SQLExceptions encountered while closing a connection are logged at
     * the WARN level and are otherwise ignored. This is important, since
     * applications such as Wabit call this method as part of their session
     * termination routine. An unchecked exception at that time would abort the
     * shutdown.
     */
    public boolean closeConMap() {
        boolean commitedOrRollBacked = true;
        final Iterator<Entry<SQLDatabase, ConnectionAndStatementBean>> iterator = conMap.entrySet().iterator();
        for (; iterator.hasNext();) {
            final Entry<SQLDatabase, ConnectionAndStatementBean> entry = iterator.next();
            try {
                Connection con = entry.getValue().getConnection();
                if (!con.isClosed()) {
                    if (!con.getAutoCommit() && entry.getValue().isConnectionUncommitted()) {
                        commitedOrRollBacked = false;
                        int result = JOptionPane.showOptionDialog(dialogOwner,
                                Messages.getString("SQLQuery.commitOrRollback", entry.getKey().getName()),
                                Messages.getString("SQLQuery.commitOrRollbackTitle"),
                                JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
                                new Object[] { Messages.getString("SQLQuery.commit"),
                                        Messages.getString("SQLQuery.rollback"), "Cancel" },
                                Messages.getString("SQLQuery.commit"));
                        if (result == JOptionPane.OK_OPTION) {
                            con.commit();
                            commitedOrRollBacked = true;
                        } else if (result == JOptionPane.NO_OPTION) {
                            con.rollback();
                            commitedOrRollBacked = true;
                        } else if (result == JOptionPane.CANCEL_OPTION) {
                            return false;
                        }
                    }
                    con.close();
                }
                iterator.remove();

            } catch (SQLException e) {
                logger.warn("Failed to close connection " + entry.getValue() + ". Skipping it.", e);
            }
        }

        return commitedOrRollBacked;
    }

    /**
     * Listens to when the an window is added or removed. This will clean up open
     * connections and remove handlers when the window is removed.
     */
    private WindowListener windowListener = new WindowAdapter() {

        public void windowClosing(WindowEvent arg0) {
            closingDialogOwner();
        }
    };

    /**
     * This Listener listens to anything that drops onto the queryTextArea
     */
    private class QueryTextAreaDropListener implements DropTargetListener {

        private final JTextArea queryArea;

        public QueryTextAreaDropListener(JTextArea textArea) {
            queryArea = textArea;
        }

        public void dragEnter(DropTargetDragEvent dtde) {
            logger.debug("We are in drag enter");
        }

        public void dragExit(DropTargetEvent dte) {
            logger.debug("We are in drag Exit");
        }

        public void dragOver(DropTargetDragEvent dtde) {
            // this would be better if there was a visible indication on the text area
            // of the caret position during the drag-over
            queryArea.setCaretPosition(queryArea.viewToModel(dtde.getLocation()));
        }

        public void drop(DropTargetDropEvent dtde) {

            DataFlavor[] flavours = dtde.getTransferable().getTransferDataFlavors();

            String[] droppedStrings = null;
            boolean isCommaSeperated = false;

            // find the first acceptable data flavor
            try {
                for (int i = 0; i < flavours.length; i++) {
                    String mimeType = flavours[i].getMimeType();
                    //if the type is DBTree
                    if (mimeType.equals("application/x-java-serialized-object; class=\"[Ljava.lang.String;\"")) {
                        dtde.acceptDrop(DnDConstants.ACTION_COPY);
                        logger.debug("Accepting drop of type: " + mimeType);
                        droppedStrings = (String[]) dtde.getTransferable().getTransferData(flavours[i]);
                        isCommaSeperated = true;
                        break;
                        //if the type is text
                    } else if (mimeType.equals("application/x-java-serialized-object; class=java.lang.String")) {
                        dtde.acceptDrop(DnDConstants.ACTION_COPY);
                        logger.debug("Accepting drop of type: " + mimeType);
                        String text = (String) dtde.getTransferable().getTransferData(flavours[i]);
                        droppedStrings = new String[] { text };
                        break;
                        //if the type is file
                    } else if (mimeType.equals("application/x-java-file-list; class=java.util.List")) {
                        dtde.acceptDrop(DnDConstants.ACTION_COPY);
                        List<?> fileList = (List<?>) dtde.getTransferable().getTransferData(flavours[i]);
                        droppedStrings = new String[fileList.size()];
                        for (int j = 0; j < droppedStrings.length; j++) {
                            StringBuffer fileContent = new StringBuffer();
                            try {
                                BufferedReader in = new BufferedReader(new FileReader(((File) fileList.get(j))));
                                String str;
                                while ((str = in.readLine()) != null) {
                                    fileContent.append(str);
                                    fileContent.append("\n");
                                }
                                droppedStrings[j] = fileContent.toString();
                                in.close();
                            } catch (IOException e) {
                                logger.debug(" Can't open file " + ((File) fileList.get(j)).getName());
                            }
                        }
                        break;
                    } else {
                        logger.debug("Unsupported flavour: " + mimeType + ". continuing...");
                    }
                }
            } catch (UnsupportedFlavorException e) {
                dtde.dropComplete(false);
                throw new IllegalStateException(
                        "DnD system says it doesn't support a data flavour" + " it already offered to us!", e);
            } catch (IOException e) {
                dtde.dropComplete(false);
                throw new RuntimeException("Drop failed due to an I/O error", e);
            }

            if (droppedStrings == null) {
                logger.debug("No supported data flavours found. Rejecting drop.");
                dtde.rejectDrop();
                return;
            }

            StringBuilder buf = new StringBuilder();
            boolean first = true;
            for (String name : droppedStrings) {
                if (!first && isCommaSeperated) {
                    buf.append(", ");
                }
                buf.append(name);
                first = false;
            }
            queryArea.insert(buf.toString(), queryArea.getCaretPosition());
            dtde.dropComplete(true);

        }

        public void dropActionChanged(DropTargetDragEvent dtde) {
            logger.debug("We are in dropActionChanged");
        }
    }

    /**
     * This listener is attached to a statement executor and is used to pass row set changes
     * from the row set to table models to update the table models.
     */
    private class StreamingRowSetListener implements RowSetChangeListener {

        private AtomicBoolean hasUpdates = new AtomicBoolean(false);

        private final Timer timer = new Timer(1000, new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                if (hasUpdates.get()) {
                    listeningTableModel.dataChanged();
                    hasUpdates.set(false);
                }
            }
        });

        /**
         * The result set this listener is listening to.
         */
        private final CachedRowSet rowSet;

        /**
         * These models will receive events of row changes when this listener receives
         * a row added event.
         */
        private final ResultSetTableModel listeningTableModel;

        /**
         * @param rs The result set this listener is listening to.
         */
        public StreamingRowSetListener(CachedRowSet rowSet, ResultSetTableModel tableModel) {
            this.rowSet = rowSet;
            listeningTableModel = tableModel;
            this.timer.setInitialDelay(0);
            this.timer.setCoalesce(true);
            this.timer.setRepeats(true);
            this.timer.start();
        }

        /**
         * This will disconnect the listener from what it is listening to and also disconnect
         * all of the tables listening to this listener.
         */
        public void disconnect() {
            this.timer.stop();
            rowSet.removeRowSetListener(this);
        }

        public void rowAdded(RowSetChangeEvent e) {
            hasUpdates.set(true);
        }
    }

    /**
     * This button will execute the sql statements in the text area.
     */
    private JButton executeButton;

    /**
     * This button will stop the execution of the currently executing statement
     * on the selected data source's connection that this panel holds.
     */
    private JButton stopButton;

    /**
     *  This button will clear the QueryTextField
     */
    private JButton clearButton;

    /**
     * Creates a SQLQueryEntryPanel and attaches a drag and drop listener
     * to a DB Tree.
     */

    /**
     * A JButton that opens up the DataBaseConnectionManager
     */
    private JButton dbcsManagerButton;

    /**
     * Creates a DataBaseConnectionManager so we can edit delete and add connections on the button 
     */
    private DatabaseConnectionManager dbConnectionManager;

    /**
     * A list of listeners that get notified when tables are
     * added or removed from the components.
     */
    private final List<TableChangeListener> tableListeners;

    /**
     * This is the document used for searching across the current result sets. This will be
     * recreated each time new results are created as it is attached to the result set JTables.
     */
    private Document searchDocument;

    /**
     * If true the search field will be shown on each result tab directly above the table. If
     * this is false then a search field can be created by retrieving the search document
     * from the tables.
     * <p>
     * This is set to true by default.
     */
    private boolean showSearchOnResults = true;

    /**
     * This statement executor will be used to run queries when the execute button is pressed.
     * This will replace the default executor for text queries and is used in at least Wabit.
     */
    private StatementExecutor stmtExecutor = null;

    /**
     * These listeners will pass events from the row set last executed to the table models
     * being displayed as results of the statement execution.
     */
    private final List<StreamingRowSetListener> rowSetListeners = new ArrayList<StreamingRowSetListener>();

    /**
     * This database mapping maps available {@link SQLDatabase} objects to 
     * corresponding {@link SPDataSource} objects. This helps prevent extra
     * connections from being created as the {@link SQLDatabase} does
     * the connection pooling.
     */
    private final SQLDatabaseMapping databaseMapping;

    /**
     * Creates all of the components of a query tool, but does not lay them out
     * in any physical configuration. Once you have created one of these
     * component collections, you can obtain all of the individual pieces and
     * put together a user interface in any way you like.
     * <p>
     * If you just want an easy way to build a full-featured query UI and don't
     * want to customize its internals, see
     * {@link #createQueryPanel(SwingWorkerRegistry, DataSourceCollection)}.
     * 
     * @param swRegistry
     *            The registry with which all background tasks will be
     *            registered. This argument must not be null.
     * @param ds
     *            The collection of data sources that will be available for
     *            querying from the UI. This argument must not be null.
     * @param panel
     *            The component whose nearest Window ancestor will own any
     *            dialogs generated by the parts of the query tool.
     * @param stmtExecutor
     *            The statement executor that will be used to execute queries
     *            instead of a default statement executor. This will be used to
     *            run queries entered in the text editor.
     */
    public SQLQueryUIComponents(SwingWorkerRegistry s, DataSourceCollection ds, SQLDatabaseMapping mapping,
            JComponent dialogOwner, StatementExecutor stmtExecutor) {
        this(s, ds, mapping, dialogOwner);
        this.stmtExecutor = stmtExecutor;
    }

    /**
     * Creates all of the components of a query tool, but does not lay them out
     * in any physical configuration. Once you have created one of these
     * component collections, you can obtain all of the individual pieces and
     * put together a user interface in any way you like.
     * <p>
     * If you just want an easy way to build a full-featured query UI and don't
     * want to customize its internals, see
     * {@link #createQueryPanel(SwingWorkerRegistry, DataSourceCollection)}.
     * 
     * @param swRegistry
     *            The registry with which all background tasks will be
     *            registered. This argument must not be null.
     * @param ds
     *            The collection of data sources that will be available for
     *            querying from the UI. This argument must not be null.
     * @param panel
     *            The component whose nearest Window ancestor will own any
     *            dialogs generated by the parts of the query tool.
     */
    public SQLQueryUIComponents(SwingWorkerRegistry s, DataSourceCollection dsCollection,
            SQLDatabaseMapping mapping, JComponent dialogOwner) {
        super();
        databaseMapping = mapping;
        previousQueries = new ArrayList<String>();
        this.dialogOwner = dialogOwner;
        this.swRegistry = s;
        this.dsCollection = dsCollection;
        this.errorTextArea.setEditable(false);
        dsCollection.addDatabaseListChangeListener(dbListChangeListener);
        resultTabPane = new JTabbedPane();
        firstResultPanel = new JPanel(new BorderLayout());
        logTextArea = new JTextArea();
        logTextArea.setEditable(false);
        logTextArea.addMouseListener(logPopUpMouseListener);
        resultTabPane.add(Messages.getString("SQLQuery.log"), new JScrollPane(logTextArea));

        resultJTables = new ArrayList<JTable>();
        tableToSQLMap = new HashMap<JTable, String>();
        tableListeners = new ArrayList<TableChangeListener>();
        dbConnectionManager = new DatabaseConnectionManager(dsCollection);

        executeAction = new AbstractSQLQueryAction(dialogOwner, Messages.getString("SQLQuery.execute")) {

            public void actionPerformed(ActionEvent e) {
                String sql = queryArea.getText();
                if (queryArea.getSelectedText() != null && queryArea.getSelectedText().trim().length() > 0) {
                    sql = queryArea.getSelectedText();
                }
                executeQuery(sql);
            }

        };

        autoCommitToggleButton = new JToggleButton(
                new AbstractSQLQueryAction(dialogOwner, Messages.getString("SQLQuery.autoCommit")) {

                    public void actionPerformed(ActionEvent e) {

                        if (databaseComboBox.getSelectedItem() == null) {
                            return;
                        }
                        Connection con = conMap
                                .get(databaseMapping
                                        .getDatabase((JDBCDataSource) databaseComboBox.getSelectedItem()))
                                .getConnection();
                        if (con == null) {
                            return;
                        }
                        try {
                            boolean isPressed = autoCommitToggleButton.getModel().isSelected();
                            if (isPressed && conMap
                                    .get(databaseMapping
                                            .getDatabase((JDBCDataSource) databaseComboBox.getSelectedItem()))
                                    .isConnectionUncommitted()) {
                                int result = JOptionPane.showOptionDialog(dialogOwner,
                                        Messages.getString("SQLQuery.commitOrRollbackBeforeAutoCommit"),
                                        Messages.getString("SQLQuery.commitOrRollbackTitle"),
                                        JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
                                        new Object[] { Messages.getString("SQLQuery.commit"),
                                                Messages.getString("SQLQuery.cancel"),
                                                Messages.getString("SQLQuery.rollback") },
                                        Messages.getString("SQLQuery.commit"));
                                if (result == JOptionPane.OK_OPTION) {
                                    commitCurrentDB();
                                } else if (result == JOptionPane.CANCEL_OPTION) {
                                    rollbackCurrentDB();
                                } else {
                                    ((JToggleButton) e.getSource()).setSelected(con.getAutoCommit());
                                    return;
                                }

                            }
                            con.setAutoCommit(isPressed);
                            logger.debug("The auto commit button is toggled " + isPressed);
                        } catch (SQLException ex) {
                            SPSUtils.showExceptionDialogNoReport(dialogOwner,
                                    Messages.getString("SQLQuery.failedAutoCommit"), ex);
                        }

                    }

                });

        autoCommitToggleButton.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                updateStatus();
            }
        });

        commitButton = new JButton(new AbstractSQLQueryAction(dialogOwner, Messages.getString("SQLQuery.commit")) {
            public void actionPerformed(ActionEvent e) {
                if (databaseComboBox.getSelectedItem() == null) {
                    return;
                }
                commitCurrentDB();
            }
        });

        rollbackButton = new JButton(
                new AbstractSQLQueryAction(dialogOwner, Messages.getString("SQLQuery.rollback")) {
                    public void actionPerformed(ActionEvent e) {
                        if (databaseComboBox.getSelectedItem() == null) {
                            return;
                        }
                        rollbackCurrentDB();
                    }
                });

        rowLimitSpinner = new JSpinner(new SpinnerNumberModel(Integer.MAX_VALUE, 0, Integer.MAX_VALUE, 1));

        queryArea = new RSyntaxTextArea();
        queryArea.restoreDefaultSyntaxScheme();
        queryArea.setSyntaxEditingStyle(RSyntaxTextArea.SYNTAX_STYLE_SQL);

        undoManager = new UndoManager();
        queryArea.getDocument().addUndoableEditListener(new UndoableEditListener() {
            public void undoableEditHappened(UndoableEditEvent e) {
                undoManager.addEdit(e.getEdit());
            }
        });
        queryArea.getActionMap().put(UNDO_SQL_EDIT, undoSQLStatementAction);
        queryArea.getInputMap().put(
                KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
                UNDO_SQL_EDIT);

        queryArea.getActionMap().put(REDO_SQL_EDIT, redoSQLStatementAction);
        queryArea.getInputMap()
                .put(KeyStroke.getKeyStroke(KeyEvent.VK_Z,
                        Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() + InputEvent.SHIFT_MASK),
                        REDO_SQL_EDIT);

        conMap = new HashMap<SQLDatabase, ConnectionAndStatementBean>();

        databaseComboBox = new JComboBox(dsCollection.getConnections(JDBCDataSource.class).toArray());
        databaseComboBox.setSelectedItem(null);
        databaseComboBox.addItemListener(new DatabaseItemListener());

        dialogOwner.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
                KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
                EXECUTE_QUERY_ACTION);
        dialogOwner.getActionMap().put(EXECUTE_QUERY_ACTION, executeAction);

        executeButton = new JButton(executeAction);

        stopButton = new JButton(new AbstractSQLQueryAction(dialogOwner, Messages.getString("SQLQuery.stop")) {
            public void actionPerformed(ActionEvent arg0) {
                ConnectionAndStatementBean conBean = conMap
                        .get(databaseMapping.getDatabase((JDBCDataSource) databaseComboBox.getSelectedItem()));
                if (conBean != null) {
                    Statement stmt = conBean.getCurrentStmt();
                    if (stmt != null) {
                        try {
                            logger.debug("stmt is being cancelled...supposely");
                            stmt.cancel();
                            if (sqlExecuteWorker != null) {
                                queuedSQLStatement = null;
                                sqlExecuteWorker.kill();
                                sqlExecuteWorker = null;
                            }
                        } catch (SQLException e) {
                            SPSUtils.showExceptionDialogNoReport(dialogOwner,
                                    Messages.getString("SQLQuery.stopException",
                                            ((SQLDatabase) databaseMapping.getDatabase(
                                                    (JDBCDataSource) databaseComboBox.getSelectedItem()))
                                                            .getName()),
                                    e);
                        }
                    }
                }
                updateStatus();
            }
        });
        clearButton = new JButton(new AbstractSQLQueryAction(dialogOwner, Messages.getString("SQLQuery.clear")) {
            public void actionPerformed(ActionEvent arg0) {
                queryArea.setText("");
            }
        });

        dbcsManagerButton = new JButton(new AbstractAction() {

            public void actionPerformed(ActionEvent e) {
                Window w = SwingUtilities.getWindowAncestor(dbcsManagerButton);
                dbConnectionManager.showDialog(w);

            }

        });

        prevQueryButton = new JButton(new AbstractAction("Prev") {
            public void actionPerformed(ActionEvent e) {
                if (prevQueryPosition > 0) {
                    prevQueryPosition--;
                    queryArea.setText(previousQueries.get(prevQueryPosition));
                }
                getPrevQueryButton().setEnabled(prevQueryPosition > 0);
                getNextQueryButton().setEnabled(prevQueryPosition < previousQueries.size() - 1);
            }
        });

        nextQueryButton = new JButton(new AbstractAction("Next") {

            public void actionPerformed(ActionEvent e) {
                if (prevQueryPosition < previousQueries.size() - 1) {
                    prevQueryPosition++;
                    queryArea.setText(previousQueries.get(prevQueryPosition));
                }
                getPrevQueryButton().setEnabled(prevQueryPosition > 0);
                getNextQueryButton().setEnabled(prevQueryPosition < previousQueries.size() - 1);
            }
        });

        getPrevQueryButton().setEnabled(false);
        getNextQueryButton().setEnabled(false);

        dbcsManagerButton.setText(Messages.getString("SQLQuery.manageConnections"));

        undoButton = new JButton(undoSQLStatementAction);
        redoButton = new JButton(redoSQLStatementAction);
        new DropTarget(queryArea, new QueryTextAreaDropListener(queryArea));

        updateStatus();
    }

    /**
     * Modifies the enabled/disabled state for the execute action as well as the
     * rollback, commit, and stop buttons. The correct state for these buttons
     * is determined by examining the state of the various components this class
     * ties together.
     */
    private void updateStatus() {

        boolean dbSelected = databaseComboBox.getSelectedItem() != null;

        executeAction.setEnabled(stmtExecutor != null || dbSelected);
        executeButton.setEnabled(stmtExecutor != null || dbSelected);

        boolean autoCommit = autoCommitToggleButton.isSelected();

        rollbackButton.setEnabled(!autoCommit && dbSelected);
        commitButton.setEnabled(!autoCommit && dbSelected);

        if (this.stmtExecutor != null) {
            stopButton.setEnabled(this.stmtExecutor.isRunning());
        } else {
            stopButton.setEnabled(false);
        }
    }

    /**
     * Executes a given query with the help of a worker. This will also clear
     * the results tabs before execution.
     * 
     * NOTE: If a query is currently executing then the query passed in will
     * execute after the current query is complete. Additionally, if there is 
     * a query already waiting to execute it will be REPLACED by the new query.
     * ie the previous query waiting to execute will not be run.
     */
    public synchronized void executeQuery(String sql) {
        if (stmtExecutor == null) {
            if (databaseComboBox.getSelectedItem() != null) {
                executeQuery(new DefaultStatementExecutor(
                        databaseMapping.getDatabase((JDBCDataSource) databaseComboBox.getSelectedItem()), sql,
                        ((Integer) rowLimitSpinner.getValue()).intValue()));
            }
        } else {
            executeQuery(stmtExecutor);
        }
    }

    /**
     * Executes a given query with the help of a worker. This will also clear
     * the results tabs before execution.
     * 
     * NOTE: If a query is currently executing then the query passed in will
     * execute after the current query is complete. Additionally, if there is 
     * a query already waiting to execute it will be REPLACED by the new query.
     * ie the previous query waiting to execute will not be run.
     */
    public synchronized void executeQuery(StatementExecutor stmtExecutor) {
        if (sqlExecuteWorker != null && !sqlExecuteWorker.isFinished()) {
            if (stmtExecutor != null) {
                queuedSQLStatement = stmtExecutor;
            }
            return;
        } else if (sqlExecuteWorker != null && sqlExecuteWorker.isFinished()) {
            if (stmtExecutor != null) {
                queuedSQLStatement = null;
            } else if (stmtExecutor == null && queuedSQLStatement != null) {
                StatementExecutor tempSQL = stmtExecutor;
                stmtExecutor = queuedSQLStatement;
                queuedSQLStatement = tempSQL;
            }
            sqlExecuteWorker.removeTaskTerminationListener(sqlExecuteTerminationListener);
            sqlExecuteWorker = null;
        }

        if (stmtExecutor == null) {
            return;
        }
        if (databaseComboBox.getSelectedIndex() == -1)
            return;
        ConnectionAndStatementBean conBean = conMap
                .get(databaseMapping.getDatabase((JDBCDataSource) databaseComboBox.getSelectedItem()));
        try {
            if (conBean != null) {
                if (!conBean.getConnection().getAutoCommit()) {
                    conBean.setConnectionUncommitted(true);
                }
            }
        } catch (SQLException e1) {
            SPSUtils.showExceptionDialogNoReport(dialogOwner, Messages.getString(
                    "SQLQuery.failedRetrievingConnection",
                    ((SQLDatabase) databaseMapping.getDatabase((JDBCDataSource) databaseComboBox.getSelectedItem()))
                            .getName()),
                    e1);
        }

        prevQueryPosition = previousQueries.size();
        previousQueries.add(stmtExecutor.getStatement());
        getPrevQueryButton().setEnabled(prevQueryPosition > 0);
        getNextQueryButton().setEnabled(prevQueryPosition < previousQueries.size() - 1);

        logger.debug("Executing SQL using executor type " + stmtExecutor.getClass());
        sqlExecuteWorker = new ExecuteSQLWorker(swRegistry, stmtExecutor);
        sqlExecuteWorker.addTaskTerminationListener(sqlExecuteTerminationListener);
        new Thread(sqlExecuteWorker).start();
    }

    /**
    * Builds the UI of the {@link SQLQueryUIComponents}. If you just want an
    * easy way to build a full-featured query UI and don't want to customize
    * its internals, you have come to the right place.
    * 
    * @param swRegistry
    *            The registry with which all background tasks will be
    *            registered. This argument must not be null.
    * @param ds
    *            The collection of data sources that will be available for
    *            querying from the UI. This argument must not be null.
    */
    public static JComponent createQueryPanel(SwingWorkerRegistry swRegistry, DataSourceCollection ds,
            SQLDatabaseMapping mapping, Window owner) {
        return createQueryPanel(swRegistry, ds, mapping, owner, null, null);
    }

    /**
     * Builds the UI of the {@link SQLQueryUIComponents}. If you just want an
     * easy way to build a full-featured query UI and don't want to customize
     * its internals, you have come to the right place. This also allows a SQL
     * string to initialize the query UI with.
     * 
     * @param swRegistry
     *            The registry with which all background tasks will be
     *            registered. This argument must not be null.
     * @param dsCollection
     *            The collection of data sources that will be available for
     *            querying from the UI. This argument must not be null.
     * 
     * @param ds
     *            The data source that the initial query will be executed on.
     *            This data source must be contained in the dsCollection and not
     *            null for the query to be executed. If the data source is null
     *            then the query will not be executed.
     * 
     * @param initialSQL
     *            The string that will be executed immediately when the query
     *            tool is shown. If this is null then no query will be executed.
     */
    public static JComponent createQueryPanel(SwingWorkerRegistry swRegistry, DataSourceCollection dsCollection,
            SQLDatabaseMapping mapping, Window owner, SQLDatabase db, String initialSQL) {

        JPanel defaultQueryPanel = new JPanel();
        SQLQueryUIComponents queryParts = new SQLQueryUIComponents(swRegistry, dsCollection, mapping,
                defaultQueryPanel);
        queryParts.addWindowListener(owner);
        JToolBar toolbar = new JToolBar();
        toolbar.setFloatable(false);
        toolbar.add(queryParts.getPrevQueryButton());
        toolbar.add(queryParts.getNextQueryButton());
        toolbar.addSeparator();
        toolbar.add(queryParts.getExecuteButton());
        toolbar.add(queryParts.getStopButton());
        toolbar.add(queryParts.getClearButton());
        toolbar.addSeparator();
        toolbar.add(queryParts.getAutoCommitToggleButton());
        toolbar.add(queryParts.getCommitButton());
        toolbar.add(queryParts.getRollbackButton());
        toolbar.addSeparator();
        toolbar.add(queryParts.getUndoButton());
        toolbar.add(queryParts.getRedoButton());

        FormLayout textAreaLayout = new FormLayout("pref:grow, 10dlu, pref, 10dlu, pref, 10dlu, pref",
                "pref, pref, fill:max(100dlu;pref):grow");
        DefaultFormBuilder textAreaBuilder = new DefaultFormBuilder(textAreaLayout, defaultQueryPanel);
        textAreaBuilder.setDefaultDialogBorder();
        textAreaBuilder.append(toolbar, 7);
        textAreaBuilder.nextLine();
        textAreaBuilder.append(queryParts.getDatabaseComboBox());
        textAreaBuilder.append(queryParts.getDbcsManagerButton());
        textAreaBuilder.append(Messages.getString("SQLQuery.rowLimit"));
        JSpinner rowlimitSpinner = queryParts.getRowLimitSpinner();
        rowlimitSpinner.setValue(new Integer(1000));
        textAreaBuilder.append(rowlimitSpinner);
        textAreaBuilder.nextLine();
        textAreaBuilder.append(new RTextScrollPane(queryParts.getQueryArea(), true), 7);

        JSplitPane queryPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);

        queryPane.add(defaultQueryPanel, JSplitPane.TOP);

        queryPane.add(queryParts.getResultTabPane(), JSplitPane.BOTTOM);

        if (db != null && initialSQL != null && dsCollection.getConnections().contains(db.getDataSource())) {
            queryParts.getDatabaseComboBox().setSelectedItem(db.getDataSource());
            queryParts.getQueryArea().setText(initialSQL);
            queryParts.executeQuery(initialSQL);
        }

        return queryPane;

    }

    /**
     * If the connection to the database currently selected in the combo box is not in 
     * auto commit mode then any changes will be committed.
     */
    private void commitCurrentDB() {
        ConnectionAndStatementBean conBean = conMap
                .get(databaseMapping.getDatabase((JDBCDataSource) databaseComboBox.getSelectedItem()));
        Connection con = conBean.getConnection();
        if (con == null) {
            return;
        }
        try {
            if (!con.getAutoCommit()) {
                con.commit();
                conBean.setConnectionUncommitted(false);
            }
        } catch (SQLException ex) {
            SPSUtils.showExceptionDialogNoReport(dialogOwner, Messages.getString("SQlQuery.failedCommit"), ex);
        }
    }

    /**
     * If the connection to the database currently selected in the combo box is not in 
     * auto commit mode then any changes will be rolled back.
     */
    private void rollbackCurrentDB() {
        ConnectionAndStatementBean conBean = conMap
                .get(databaseMapping.getDatabase((JDBCDataSource) databaseComboBox.getSelectedItem()));
        Connection con = conBean.getConnection();
        if (con == null) {
            return;
        }
        try {
            if (!con.getAutoCommit()) {
                con.rollback();
                conBean.setConnectionUncommitted(false);
            }
        } catch (SQLException ex) {
            SPSUtils.showExceptionDialogNoReport(dialogOwner, Messages.getString("SQLQuery.failedRollback"), ex);
        }
    }

    /**
     * Creates all of the JTables for the result tab and adds them to the result tab.
     * @throws SQLException 
     */
    private synchronized void createResultSetTables(List<CachedRowSet> resultSets, StatementExecutor executor)
            throws SQLException {
        clearResultTables(true);
        for (StreamingRowSetListener rowSetListener : rowSetListeners) {
            rowSetListener.disconnect();
        }
        rowSetListeners.clear();

        searchDocument = new DefaultStyledDocument();
        for (CachedRowSet rs : resultSets) {
            final JTable tempTable;
            FormLayout tableAreaLayout = new FormLayout("pref, 3dlu, pref:grow", "pref, fill:min(pref;50dlu):grow");
            DefaultFormBuilder tableAreaBuilder = new DefaultFormBuilder(tableAreaLayout);

            if (showSearchOnResults) {
                JLabel searchLabel = new JLabel(ICON);
                searchLabel.setToolTipText("Search");
                JTextField tableFilterTextField = new JTextField(searchDocument, null, 0);
                tableAreaBuilder.append(searchLabel, tableFilterTextField);
            }
            ResultSetTableModel model = new ResultSetTableModel(rs);
            StreamingRowSetListener rowSetListener = new StreamingRowSetListener(rs, model);
            rs.addRowSetListener(rowSetListener);
            rowSetListeners.add(rowSetListener);

            tempTable = new FancyExportableJTable(model, searchDocument);
            final TableModelListener tableListener = new TableModelListener() {
                public void tableChanged(TableModelEvent e) {
                    tempTable.createDefaultColumnsFromModel();
                }
            };
            model.addTableModelListener(tableListener);

            // Allow users to select a single table cell.
            tempTable.setCellSelectionEnabled(true);

            tableAreaBuilder.nextLine();
            JScrollPane tableScrollPane = new JScrollPane(tempTable);
            tableScrollPane.setPreferredSize(new Dimension((int) tableScrollPane.getPreferredSize().getWidth(), 0));
            resultJTableScrollPanes.put(tempTable, tableScrollPane);
            tableAreaBuilder.append(tableScrollPane, 3);

            resultJTables.add((JTable) tempTable);
            tableToSQLMap.put(((JTable) tempTable), executor.getStatement());
            JPanel tempResultPanel = tableAreaBuilder.getPanel();
            resultTabPane.add(Messages.getString("SQLQuery.result"), tempResultPanel);
            resultTabPane.setSelectedIndex(1);

        }
        for (JTable table : resultJTables) {
            for (TableChangeListener l : tableListeners) {
                l.tableAdded(new TableChangeEvent(this, table));
            }
        }
    }

    /**
     * Removes all of the result tables that have been created by this class.
     * All existing table listeners will be sent a tableRemoved event.
     * 
     * @see #tableListeners
     * @param removeTabs
     *            If true the tabs that the result tables were in will be
     *            removed. If false, the notification will still take place,
     *            but the tabs (and the result set viewers associated with
     *            them) will be left in place. In that case, a subsequent call
     *            to this method will send the tableRemoved event to the tableListeners
     *            notifying about the same table being removed again.
     */
    private void clearResultTables(boolean removeTabs) {
        tableToSQLMap.clear();
        for (JTable table : resultJTables) {
            for (int i = tableListeners.size() - 1; i >= 0; i--) {
                tableListeners.get(i).tableRemoved(new TableChangeEvent(this, table));
            }
        }

        if (removeTabs) {
            resultJTables.clear();
            resultJTableScrollPanes.clear();
            if (resultTabPane.getComponentCount() > 1) {
                for (int i = resultTabPane.getComponentCount() - 1; i >= 1; i--) {
                    resultTabPane.remove(i);
                }
            }
        }
    }

    /**
     * This will add a connection to the map of known connections.
     * 
     * <p>This is package private for testing.
     */
    void addConnection(SQLDatabase db) throws SQLObjectException {
        if (!conMap.containsKey(db)) {
            Connection con = db.getConnection();
            conMap.put(db, new ConnectionAndStatementBean(con));
        }
        try {
            autoCommitToggleButton.setSelected(conMap.get(db).getConnection().getAutoCommit());
        } catch (SQLException ex) {
            SPSUtils.showExceptionDialogNoReport(dialogOwner, Messages.getString("SQLQuery.failedConnectingToDB"),
                    ex);
        }
        updateStatus();
        logTextArea.append("\n" + JDBCDataSource.getConnectionInfoString(db.getDataSource(), false) + "\n\n");
        logTextArea.setCaretPosition(logTextArea.getDocument().getLength());
    }

    public void addWindowListener(Window container) {
        container.addWindowListener(windowListener);
    }

    public JButton getExecuteButton() {
        return executeButton;
    }

    public JButton getStopButton() {
        return stopButton;
    }

    public JButton getClearButton() {
        return clearButton;
    }

    public JToggleButton getAutoCommitToggleButton() {
        return autoCommitToggleButton;
    }

    public JButton getCommitButton() {
        return commitButton;
    }

    public JButton getRollbackButton() {
        return rollbackButton;
    }

    public JButton getUndoButton() {
        return undoButton;
    }

    public JButton getRedoButton() {
        return redoButton;
    }

    public JComboBox getDatabaseComboBox() {
        return databaseComboBox;
    }

    public JButton getDbcsManagerButton() {
        return dbcsManagerButton;
    }

    public JSpinner getRowLimitSpinner() {
        return rowLimitSpinner;
    }

    public void setRowLimitSpinner(JSpinner newRowLimitSpinner) {
        rowLimitSpinner = newRowLimitSpinner;
    }

    public RSyntaxTextArea getQueryArea() {
        return queryArea;
    }

    public JTabbedPane getResultTabPane() {
        return resultTabPane;
    }

    public ArrayList<JTable> getResultTables() {
        return resultJTables;
    }

    public void addTableChangeListener(TableChangeListener l) {
        tableListeners.add(l);
    }

    public void removeTableChangeListener(TableChangeListener l) {
        tableListeners.remove(l);
    }

    public JTextArea getLogTextArea() {
        return logTextArea;
    }

    public JPanel getFirstResultPanel() {
        return firstResultPanel;
    }

    /**
     * This will return the query that made the JTable's result set.
     * If this returns null then the table has already been removed from the
     * results tab.
     */
    public String getQueryForJTable(JTable table) {
        return tableToSQLMap.get(table);
    }

    public void disconnectListeners() {
        dsCollection.removeDatabaseListChangeListener(dbListChangeListener);
        for (StreamingRowSetListener rowSetListener : rowSetListeners) {
            rowSetListener.disconnect();
        }
    }

    public Document getSearchDocument() {
        return searchDocument;
    }

    public void setShowSearchOnResults(boolean showSearchOnResults) {
        this.showSearchOnResults = showSearchOnResults;
    }

    /**
     * Sets the data source combo box to the given data source.
     * 
     * <p> This is used for testing.
     */
    void setCurrentDataSource(SPDataSource ds) {
        databaseComboBox.getModel().setSelectedItem(ds);
    }

    /**
     * Gets the currently executing SQL worker or null if none are currently executing.
     * 
     * <p>Used in testing.
     */
    ExecuteSQLWorker getSqlExecuteWorker() {
        return sqlExecuteWorker;
    }

    public JButton getNextQueryButton() {
        return nextQueryButton;
    }

    public JButton getPrevQueryButton() {
        return prevQueryButton;
    }

    /**
     * This will create the an error Message String similar to the details in the Exception Dialog.
     */
    public static String createErrorStringMessage(Throwable e) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter traceWriter = new PrintWriter(stringWriter);
        stringWriter.write(Messages.getString("SQLQuery.queryFailed"));
        e.printStackTrace(traceWriter);
        stringWriter.write("\n\n");
        stringWriter
                .write(Messages.getString("SQLQuery.queryFailedSeeAbove", SPSUtils.getRootCause(e).getMessage()));
        return stringWriter.toString();
    }
}