ca.sqlpower.architect.swingui.SQLScriptDialog.java Source code

Java tutorial

Introduction

Here is the source code for ca.sqlpower.architect.swingui.SQLScriptDialog.java

Source

/*
 * Copyright (c) 2008, SQL Power Group Inc.
 *
 * This file is part of Power*Architect.
 *
 * Power*Architect 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.
 *
 * Power*Architect 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.architect.swingui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.HeadlessException;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;

import org.apache.log4j.Logger;

import ca.sqlpower.architect.ddl.DDLGenerator;
import ca.sqlpower.architect.ddl.DDLStatement;
import ca.sqlpower.sql.JDBCDataSource;
import ca.sqlpower.sqlobject.SQLDatabase;
import ca.sqlpower.sqlobject.SQLObjectException;
import ca.sqlpower.swingui.ProgressWatcher;
import ca.sqlpower.swingui.SPSUtils;
import ca.sqlpower.swingui.SPSwingWorker;
import ca.sqlpower.swingui.SPSUtils.FileExtensionFilter;

import com.jgoodies.forms.builder.ButtonBarBuilder;
import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.debug.FormDebugPanel;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

/**
 * A collection of components that present a series of SQL statements (a
 * "script") to the user, and allows the user to execute the script against a
 * pre-arranged target database, copy it to the system clipboard, or save it to
 * a file.
 */
public class SQLScriptDialog extends JDialog {

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

    private List<DDLStatement> statements;
    private JProgressBar progressBar = new JProgressBar();

    private Component parent;
    private String header;

    private JLabel statusLabel;

    private JDBCDataSource targetDataSource;

    private JTextPane sqlScriptArea;
    private AbstractDocument sqlDoc;

    private boolean closeParent;

    private ArchitectSwingSession session;

    private SPSwingWorker executeTask;

    private JButton executeButton;

    /**
     * Creates and packs a new SQL script dialog, but does not display it. Call
     * setVisible(true) to show the dialog, which will appear over or near the
     * given owner.
     * 
     * @param owner
     *            The dialog that owns this dialog. Can be null only if this
     *            dialog is not modal.
     * @param title
     *            The text to show in the dialog's title bar.
     * @param header
     *            The text to show inside the dialog body above the SQL
     *            statements.
     * @param modal
     *            Incidates if the generated dialog should be application-modal.
     *            If this is true, the owner parameter must not be null.
     * @param gen
     *            The DDL generator that supplies the SQL script. The script
     *            will be obtained by a call to
     *            {@link DDLGenerator#getDdlStatements()}.
     *            XXX: this should be a List of DDLStatement instead
     * @param targetDataSource
     *            The database to execute the statements in. This can be null,
     *            in which case the execute button will not function. Save and
     *            copy will still work.
     * @param closeParent
     *            If true, this dialog's owner will be closed when this dialog
     *            closes. XXX: this is probably not the best place for this feature
     * @param session
     *            The session that provides the SPSwingWorkerRegistry.
     *            XXX: this should be specified as a SwingWorkerRegistry
     */
    public SQLScriptDialog(Dialog owner, String title, String header, boolean modal, DDLGenerator gen,
            JDBCDataSource targetDataSource, boolean closeParent, ArchitectSwingSession session)
            throws HeadlessException {
        super(owner, title, modal);
        if (modal && owner == null) {
            JOptionPane.showMessageDialog(null, "Debug: This action tried to create a null-parented modal dialog"); //$NON-NLS-1$
        }
        statusLabel = new JLabel();
        parent = owner;
        this.header = header;
        this.statements = gen.getDdlStatements();
        this.targetDataSource = targetDataSource;
        this.closeParent = closeParent;
        this.session = session;
        this.executeTask = new ExecuteSQLScriptWorker(session);
        logger.info("The list size is :" + statements.size()); //$NON-NLS-1$
        add(buildPanel());
        pack();
        setLocationRelativeTo(parent);
    }

    private JPanel buildPanel() {
        FormLayout sqlLayout = new FormLayout("4dlu, min:grow, 4dlu", //columns //$NON-NLS-1$
                "pref, 4dlu, pref, 6dlu, fill:300dlu:grow,6dlu, pref, 6dlu, pref"); //rows //$NON-NLS-1$

        CellConstraints cc = new CellConstraints();

        sqlDoc = new DefaultStyledDocument();

        SimpleAttributeSet att = new SimpleAttributeSet();
        StyleConstants.setForeground(att, Color.black);

        for (DDLStatement ddl : statements) {
            try {
                sqlDoc.insertString(sqlDoc.getLength(), ddl.getSQLText() + ddl.getSqlTerminator(), att);
            } catch (BadLocationException e) {
                ASUtils.showExceptionDialogNoReport(parent,
                        Messages.getString("SQLScriptDialog.couldNotCreateDocument"), e); //$NON-NLS-1$
                logger.error("Could not create document for results", e); //$NON-NLS-1$
            }
        }
        sqlScriptArea = new JTextPane();
        sqlScriptArea.setMargin(new Insets(6, 10, 4, 6));
        sqlScriptArea.setDocument(sqlDoc);
        sqlScriptArea.setEditable(false);
        sqlScriptArea.setAutoscrolls(true);
        JScrollPane sp = new JScrollPane(sqlScriptArea);

        Action copy = new CopyAction(sqlDoc);
        Action execute = null;

        execute = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                if (targetDataSource.get(JDBCDataSource.PL_UID) != null) {
                    new Thread(executeTask).start();
                    ProgressWatcher.watchProgress(progressBar, executeTask, statusLabel);
                } else {
                    JOptionPane.showMessageDialog(SQLScriptDialog.this,
                            Messages.getString("SQLScriptDialog.noTargetDb"), //$NON-NLS-1$
                            Messages.getString("SQLScriptDialog.couldNotExecuteDialogTitle"), //$NON-NLS-1$
                            JOptionPane.ERROR_MESSAGE);
                }
            }
        };

        Action save = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {

                logger.info("SQL_FILE_FILTER:" + ((FileExtensionFilter) SPSUtils.SQL_FILE_FILTER).toString()); //$NON-NLS-1$

                SPSUtils.saveDocument(parent, sqlDoc, (FileExtensionFilter) SPSUtils.SQL_FILE_FILTER);
            }
        };
        CloseAction close = new CloseAction();
        close.setWhatToClose(this);
        SPSUtils.makeJDialogCancellable(this, close);

        ButtonBarBuilder barBuilder = new ButtonBarBuilder();
        JButton copyButton = new JButton(copy);
        copyButton.setText(Messages.getString("SQLScriptDialog.copyOption")); //$NON-NLS-1$
        barBuilder.addGridded(copyButton);
        barBuilder.addRelatedGap();
        barBuilder.addGlue();

        executeButton = new JButton(execute);
        executeButton.setText(Messages.getString("SQLScriptDialog.executeOption")); //$NON-NLS-1$
        barBuilder.addGridded(executeButton);
        barBuilder.addRelatedGap();
        barBuilder.addGlue();

        JButton saveButton = new JButton(save);
        saveButton.setText(Messages.getString("SQLScriptDialog.saveOption")); //$NON-NLS-1$
        barBuilder.addGridded(saveButton);
        barBuilder.addRelatedGap();
        barBuilder.addGlue();

        addWindowListener(new CloseWindowAction());
        JButton closeButton = new JButton(close);
        closeButton.setText(Messages.getString("SQLScriptDialog.closeOption")); //$NON-NLS-1$
        barBuilder.addGridded(closeButton);
        getRootPane().setDefaultButton(executeButton);

        PanelBuilder pb;

        JPanel panel = logger.isDebugEnabled() ? new FormDebugPanel(sqlLayout) : new JPanel(sqlLayout);
        pb = new PanelBuilder(sqlLayout, panel);
        pb.setDefaultDialogBorder();
        pb.add(new JLabel(header), cc.xy(2, 1));

        // Prevent the user from being able to execute the script if
        // an invalid target database is selected.
        if (targetDataSource != null && targetDataSource.get(JDBCDataSource.PL_UID) != null) {
            pb.add(new JLabel(Messages.getString("SQLScriptDialog.yourTargetDbIs") + targetDataSource.getName()), //$NON-NLS-1$
                    cc.xy(2, 3));
            executeButton.setEnabled(true);
        } else {
            pb.add(new JLabel(Messages.getString("SQLScriptDialog.yourTargetDbIsNotConfigured")), cc.xy(2, 3));
            executeButton.setEnabled(false);
        }
        pb.add(sp, cc.xy(2, 5));
        pb.add(barBuilder.getPanel(), cc.xy(2, 7, "c,c")); //$NON-NLS-1$
        pb.add(progressBar, cc.xy(2, 9));

        return pb.getPanel();
    }

    public SPSwingWorker getExecuteTask() {
        return executeTask;
    }

    /**
     * Changes the task that will be invoked by the "execute" button on this
     * dialog.  If you want your task to run before the normal script execution,
     * you should call {@link #getExecuteTask()} and chain that task onto the
     * one you specify here.
     *
     * @param v The task to execute when the "execute" button is clicked.
     */
    public void setExecuteTask(SPSwingWorker v) {
        executeTask = v;
    }

    // ============== Nested classes follow ================

    private class CopyAction extends AbstractAction {

        AbstractDocument doc;

        public CopyAction(AbstractDocument doc) {
            this.doc = doc;
        }

        public void actionPerformed(ActionEvent e) {

            try {
                StringSelection selection = new StringSelection(doc.getText(0, doc.getLength()));
                Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection);
            } catch (BadLocationException e1) {
                logger.debug("Unable to get the text for copying" + e1); //$NON-NLS-1$
            }

        }
    }

    /**
     * An action that will close the parent dialog when this window
     * is closed iff the closeParent variable was set when the dialog 
     * was constructed 
     *
     */
    private class CloseWindowAction extends WindowAdapter {
        public void windowClosing(WindowEvent e) {
            if (closeParent) {
                parent.setVisible(false);
            }
        }
    }

    private class CloseAction extends AbstractAction {

        Component c;

        public void setWhatToClose(Component c) {
            this.c = c;
        }

        public void actionPerformed(ActionEvent e) {
            c.setVisible(false);
            if (closeParent)
                parent.setVisible(false);
        }
    }

    private class ExecuteSQLScriptWorker extends SPSwingWorker {

        private int stmtsCompleted = 0;

        public ExecuteSQLScriptWorker(ArchitectSwingSession session) {
            super(session);
            setMessage(null);
            if (statements != null) {
                setJobSize(new Integer(statements.size()));
            } else {
                setJobSize(null);
            }
        }

        /**
         * This method runs on a separate worker thread.
         */
        public void doStuff() {

            setCancelled(false);
            if (isCancelled() || isFinished())
                return;

            SQLDatabase target = new SQLDatabase(targetDataSource);
            statusLabel.setText(
                    Messages.getString("SQLScriptDialog.creatingObjectsInTargetDb") + target.getDataSource()); //$NON-NLS-1$
            setProgress(0);
            stmtsCompleted = 0;

            logger.debug("the Target Database is: " + target.getDataSource()); //$NON-NLS-1$

            Connection con;
            Statement stmt;

            try {
                con = target.getConnection();
            } catch (SQLObjectException ex) {
                setFinished(true);
                throw new RuntimeException(
                        Messages.getString("SQLScriptDialog.couldNotConnectToTargetDb", ex.getMessage()), ex); //$NON-NLS-1$
            } catch (Exception ex) {
                setFinished(true);
                logger.error("Unexpected exception in DDL generation", ex); //$NON-NLS-1$
                throw new RuntimeException(Messages.getString("SQLScriptDialog.specifyATargetDb")); //$NON-NLS-1$
            }

            try {
                logger.debug("the connection thinks it is: " + con.getMetaData().getURL()); //$NON-NLS-1$
                stmt = con.createStatement();
            } catch (SQLException ex) {
                setFinished(true);
                throw new RuntimeException(
                        Messages.getString("SQLScriptDialog.couldNotGenerateDDL", ex.getMessage())); //$NON-NLS-1$
            }

            try {
                logger.info("Starting DDL Generation at " + new java.util.Date(System.currentTimeMillis())); //$NON-NLS-1$
                logger.info("Database Target: " + target.getDataSource()); //$NON-NLS-1$
                logger.info("Playpen Dump: " + target.getDataSource()); //$NON-NLS-1$

                SQLScriptDialog.this.executeButton.setEnabled(false);
                Iterator<DDLStatement> it = statements.iterator();
                while (it.hasNext() && !isFinished() && !isCancelled()) {
                    DDLStatement ddlStmt = it.next();
                    try {
                        increaseProgress();
                        logger.info("executing: " + ddlStmt.getSQLText()); //$NON-NLS-1$
                        stmt.executeUpdate(ddlStmt.getSQLText());
                        stmtsCompleted++;
                    } catch (final SQLException ex) {
                        final String fsql = ddlStmt.getSQLText() == null ? null : ddlStmt.getSQLText().trim();
                        logger.info("sql statement failed: " + ex.getMessage()); //$NON-NLS-1$
                        try {
                            SwingUtilities.invokeAndWait(new Runnable() {
                                public void run() {
                                    JTextArea jta = new JTextArea(fsql);
                                    jta.setOpaque(false);
                                    jta.setEditable(false);
                                    JPanel jp = new JPanel(new BorderLayout(0, 10));
                                    jp.add(new JLabel(Messages.getString("SQLScriptDialog.sqlStatementFailed",
                                            ex.getMessage())), BorderLayout.NORTH);
                                    jp.add(jta, BorderLayout.CENTER);
                                    jp.add(new JLabel(Messages.getString("SQLScriptDialog.continuePrompt")),
                                            BorderLayout.SOUTH);
                                    int decision = JOptionPane.showConfirmDialog(SQLScriptDialog.this, jp,
                                            Messages.getString("SQLScriptDialog.sqlFailure"), //$NON-NLS-1$
                                            JOptionPane.YES_NO_OPTION);
                                    if (decision == JOptionPane.NO_OPTION) {
                                        logger.info("Export cancelled by user."); //$NON-NLS-1$
                                        cancelJob();
                                    }
                                }
                            });
                        } catch (InterruptedException ex2) {
                            logger.warn("DDL Worker was interrupted during InvokeAndWait", ex2); //$NON-NLS-1$
                        } catch (InvocationTargetException ex2) {
                            throw new RuntimeException(ex2);
                        }

                        if (isCancelled()) {
                            setFinished(true);
                            // don't return, we might as well display how many statements ended up being processed...
                        }
                    }
                }

            } catch (Exception exc) {
                logger.info("Caught Unexpected Exception " + exc); //$NON-NLS-1$
                ASUtils.showExceptionDialog(session, Messages.getString("SQLScriptDialog.couldNotFinishSQL"), //$NON-NLS-1$
                        exc);
            } finally {
                final String resultsMessage = (stmtsCompleted == 0
                        ? Messages.getString("SQLScriptDialog.didNotExecute", String.valueOf(getProgress())) //$NON-NLS-1$
                        : Messages.getString("SQLScriptDialog.successfullyExecuted", String.valueOf(stmtsCompleted), //$NON-NLS-1$
                                String.valueOf(getProgress())));
                logger.info(resultsMessage);
                JOptionPane.showMessageDialog(SQLScriptDialog.this, resultsMessage);
                // flush and close the LogWriter
                try {
                    if (stmt != null)
                        stmt.close();
                } catch (SQLException ex) {
                    logger.error("SQLException while closing statement", ex); //$NON-NLS-1$
                }
                try {
                    if (con != null)
                        con.close();
                } catch (SQLException ex) {
                    logger.error("Couldn't close connection", ex); //$NON-NLS-1$
                }
                SQLScriptDialog.this.executeButton.setEnabled(true);
            }

        }

        /**
         * Displays error messages or invokes the next process in the chain on a new
         * thread. The run method asks swing to invoke this method on the event dispatch
         * thread after it's done.
         */
        public void cleanup() {

        }

        // ============= Monitorable Interface =============

        public void cancelJob() {
            this.setCancelled(true);
            setFinished(true);
        }

    }
}