ca.sqlpower.architect.swingui.action.ExportDDLAction.java Source code

Java tutorial

Introduction

Here is the source code for ca.sqlpower.architect.swingui.action.ExportDDLAction.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.action;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.Callable;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

import org.apache.log4j.Logger;

import ca.sqlpower.architect.ddl.ConflictResolver;
import ca.sqlpower.architect.ddl.DDLGenerator;
import ca.sqlpower.architect.ddl.DDLStatement;
import ca.sqlpower.architect.ddl.critic.CriticFix;
import ca.sqlpower.architect.ddl.critic.Criticism;
import ca.sqlpower.architect.ddl.critic.CriticismBucket;
import ca.sqlpower.architect.ddl.critic.CriticFix.FixType;
import ca.sqlpower.architect.swingui.ASUtils;
import ca.sqlpower.architect.swingui.ArchitectFrame;
import ca.sqlpower.architect.swingui.ArchitectSwingSession;
import ca.sqlpower.architect.swingui.DDLExportPanel;
import ca.sqlpower.architect.swingui.SQLScriptDialog;
import ca.sqlpower.architect.swingui.critic.CriticSwingUtil;
import ca.sqlpower.sqlobject.SQLDatabase;
import ca.sqlpower.sqlobject.SQLObjectException;
import ca.sqlpower.swingui.DataEntryPanelBuilder;
import ca.sqlpower.swingui.SPSwingWorker;
import ca.sqlpower.swingui.table.TableUtils;

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

public class ExportDDLAction extends AbstractArchitectAction {

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

    private static final String GENDDL_WARNINGS_EXPLANATION = Messages
            .getString("ExportDDLAction.errorsInstructions"); //$NON-NLS-1$

    private JDialog d;

    public ExportDDLAction(final ArchitectFrame frame) {
        super(frame, Messages.getString("ExportDDLAction.name"), Messages.getString("ExportDDLAction.description"), //$NON-NLS-1$//$NON-NLS-2$
                "fwdSQL"); //$NON-NLS-1$
    }

    public void actionPerformed(ActionEvent e) {
        final DDLExportPanel ddlPanel = new DDLExportPanel(getSession());

        Callable<Boolean> okCall, cancelCall;
        okCall = new Callable<Boolean>() {
            public Boolean call() {
                if (ddlPanel.applyChanges()) {

                    DDLGenerator ddlg = ddlPanel.getGenerator();
                    ddlg.setTargetSchema(ddlPanel.getSchemaField().getText());

                    checkErrorsAndGenerateDDL(ddlg);

                }
                return Boolean.FALSE;
            }

            /**
             * This method will run the known critics over the target database
             * that we want to generate a DDL script for and display a dialog
             * containing errors if any are found. The user will then have the
             * choice to fix their data model or continue on ignoring the
             * current set of errors.
             * <p>
             * This method will also generate the DDL script using the
             * generateAndDisplayDDL method.
             */
            private void checkErrorsAndGenerateDDL(final DDLGenerator ddlg) {
                List<Criticism> criticisms = getSession().getWorkspace().getCriticManager()
                        .criticize(ddlg.getClass());
                if (criticisms.isEmpty()) {
                    try {
                        generateAndDisplayDDL(ddlPanel, ddlg);
                    } catch (Exception ex) {
                        ASUtils.showExceptionDialog(getSession(),
                                Messages.getString("ExportDDLAction.errorGeneratingDDLScript"), ex); //$NON-NLS-1$
                    }
                } else {
                    //build warning dialog
                    final JDialog warningDialog = new JDialog(frame);
                    JPanel mainPanel = new JPanel();
                    DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("pref:grow"), mainPanel);
                    builder.setDefaultDialogBorder();
                    JTextArea explanation = new JTextArea(GENDDL_WARNINGS_EXPLANATION, 8, 60);
                    explanation.setLineWrap(true);
                    explanation.setWrapStyleWord(true);
                    explanation.setEditable(false);
                    explanation.setBackground(mainPanel.getBackground());
                    explanation.setPreferredSize(new Dimension(0, 0));
                    builder.append(explanation);

                    builder.appendRow("10dlu");
                    builder.nextLine();

                    builder.appendRow("fill:pref:grow");
                    builder.nextLine();

                    final CriticismBucket bucket = new CriticismBucket();
                    bucket.updateCriticismsToMatch(criticisms);
                    JTable errorTable = CriticSwingUtil.createCriticTable(getSession(), bucket);
                    builder.append(new JScrollPane(errorTable));
                    builder.nextLine();

                    JButton quickFixButton = new JButton(
                            new AbstractAction(Messages.getString("ExportDDLAction.quickFixAllOption")) { //$NON-NLS-1$
                                public void actionPerformed(ActionEvent e) {
                                    warningDialog.dispose();
                                    for (Criticism criticism : bucket.getCriticisms()) {
                                        if (!criticism.getFixes().isEmpty()) {
                                            for (CriticFix fix : criticism.getFixes()) {
                                                if (fix.getFixType().equals(FixType.QUICK_FIX)) {
                                                    fix.apply();
                                                    //applying the first quick fix each time as there is no 
                                                    //decision what to apply by the user for this case
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    checkErrorsAndGenerateDDL(ddlg);
                                }
                            });
                    JButton ignoreButton = new JButton(
                            new AbstractAction(Messages.getString("ExportDDLAction.ignoreWarningsOption")) { //$NON-NLS-1$
                                public void actionPerformed(ActionEvent e) {
                                    warningDialog.dispose();
                                    try {
                                        generateAndDisplayDDL(ddlPanel, ddlg);
                                    } catch (Exception ex) {
                                        ASUtils.showExceptionDialog(getSession(),
                                                Messages.getString("ExportDDLAction.errorGeneratingDDLScript"), ex); //$NON-NLS-1$
                                    }
                                }
                            });
                    JButton cancelButton = new JButton(
                            new AbstractAction(Messages.getString("ExportDDLAction.cancelOption")) { //$NON-NLS-1$
                                public void actionPerformed(ActionEvent e) {
                                    //just dispose of the dialog and end this.
                                    warningDialog.dispose();
                                }
                            });
                    JButton recheckButton = new JButton(
                            new AbstractAction(Messages.getString("ExportDDLAction.recheckOption")) { //$NON-NLS-1$
                                public void actionPerformed(ActionEvent e) {
                                    warningDialog.dispose();
                                    checkErrorsAndGenerateDDL(ddlg);
                                }
                            });

                    ButtonBarBuilder buttonBar = new ButtonBarBuilder();
                    buttonBar.addGlue();
                    buttonBar.addGriddedButtons(
                            new JButton[] { quickFixButton, ignoreButton, cancelButton, recheckButton });

                    builder.append(buttonBar.getPanel());
                    warningDialog.add(mainPanel);

                    warningDialog.pack();
                    TableUtils.fitColumnWidths(errorTable, 10);
                    errorTable.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
                    warningDialog.setLocationRelativeTo(frame);
                    warningDialog.setVisible(true);
                }
            }

            /**
             * This method is used for generating and displaying the DDL script
             * for the current target database using the given DDL generator.
             */
            private void generateAndDisplayDDL(final DDLExportPanel ddlPanel, DDLGenerator ddlg)
                    throws SQLException, SQLObjectException {
                ddlg.generateDDLScript(getSession(), getSession().getTargetDatabase().getTables());

                SQLDatabase ppdb = new SQLDatabase(ddlPanel.getTargetDB());
                SQLScriptDialog ssd = new SQLScriptDialog(d,
                        Messages.getString("ExportDDLAction.previewSQLScriptDialogTitle"), "", false, //$NON-NLS-1$ //$NON-NLS-2$
                        ddlg, ppdb.getDataSource(), true, getSession());
                SPSwingWorker scriptWorker = ssd.getExecuteTask();
                ConflictFinderProcess cfp = new ConflictFinderProcess(ssd, ppdb, ddlg, ddlg.getDdlStatements(),
                        getSession());
                ConflictResolverProcess crp = new ConflictResolverProcess(ssd, cfp, getSession());
                cfp.setNextProcess(crp);
                crp.setNextProcess(scriptWorker);
                ssd.setExecuteTask(cfp);
                ssd.setVisible(true);
            }
        };

        cancelCall = new Callable<Boolean>() {
            public Boolean call() {
                ddlPanel.discardChanges();
                return Boolean.TRUE;
            }
        };
        d = DataEntryPanelBuilder.createDataEntryPanelDialog(ddlPanel, frame,
                Messages.getString("ExportDDLAction.forwardEngineerSQLDialogTitle"), //$NON-NLS-1$
                Messages.getString("ExportDDLAction.okOption"), //$NON-NLS-1$
                okCall, cancelCall);

        d.pack();
        d.setLocationRelativeTo(frame);
        d.setVisible(true);
    }

    /**
     * The ConflictFinderProcess uses a ConflictResolver (which it monitors with
     * a progress bar) to locate objects in the target database which need to be
     * removed before a set of DDL statements can be executed in it.
     *
     * @author fuerth
     */
    public class ConflictFinderProcess extends SPSwingWorker {

        JDialog parentDialog;
        SQLDatabase target;
        DDLGenerator ddlg;
        List<DDLStatement> statements;

        /**
         * This Conflict Resolver is created and populated by the run() method.
         */
        ConflictResolver cr;

        /**
         * If something goes wrong in the run() method, the runFinished() method will
         * be given a message to display in this string.
         */
        String errorMessage;

        /**
         * If something throws an exception in the run() method, it is saved here and
         * displayed in runFinished().
         */
        Throwable error;
        private boolean shouldDropConflicts;

        /**
         * @param parentDialog The JDialog we're doing this in.
         * @param target The target database (where to search for the conflicts).
         * @param ddlg The DDL Generator that we're using.
         * @throws SQLObjectException If there is a problem connecting to the target database
         * @throws SQLException If the conflict resolver chokes
         */
        public ConflictFinderProcess(JDialog parentDialog, SQLDatabase target, DDLGenerator ddlg,
                List<DDLStatement> statements, ArchitectSwingSession session) throws SQLObjectException {
            super(session);
            this.parentDialog = parentDialog;
            this.target = target;
            this.ddlg = ddlg;
            this.statements = statements;

            cr = new ConflictResolver(target, ddlg, statements);
        }

        /**
         * @return True if and only if the user has asked for the conflicts to
         *         be deleted.
         */
        public boolean doesUserWantToDropConflicts() {
            return shouldDropConflicts;
        }

        /**
         * This method is called on its own thread (not the AWT event dispatch
         * thread). It will take a while.
         */
        public void doStuff() {

            if (this.isCancelled())
                return;

            // First, test if it's possible to connect to the target database
            Connection con = null;
            try {
                con = target.getConnection();
            } catch (Exception ex) {
                error = ex;
                errorMessage = Messages.getString("ExportDDLAction.failedToConnectToDb"); //$NON-NLS-1$
                return;
            } finally {
                if (con != null) {
                    try {
                        con.close();
                    } catch (SQLException ex) {
                        logger.error("Failed to close connection. This exception is getting squashed:", ex); //$NON-NLS-1$
                    }
                }
            }

            // Now do the actual work
            try {
                cr.findConflicting();
            } catch (Exception ex) {
                error = ex;
                errorMessage = Messages.getString("ExportDDLAction.unexpectedException"); //$NON-NLS-1$
                logger.error("Unexpected exception setting up DDL generation", ex); //$NON-NLS-1$
            }
        }

        /**
         * After the doStuff() method is done, this method will be invoked on the AWT event
         * dispatch thread.
         */
        public void cleanup() {
            if (!SwingUtilities.isEventDispatchThread()) {
                logger.error("runFinished is running on the wrong thread!"); //$NON-NLS-1$
            }
            if (errorMessage != null) {
                if (error != null) {
                    ASUtils.showExceptionDialogNoReport(parentDialog, errorMessage, error);
                } else {
                    JOptionPane.showMessageDialog(parentDialog, errorMessage,
                            Messages.getString("ExportDDLAction.errorMessageDialogTitle"), //$NON-NLS-1$
                            JOptionPane.ERROR_MESSAGE);
                }
            } else if (!cr.isEmpty()) {
                Object[] messages = new Object[3];
                messages[0] = Messages.getString("ExportDDLAction.conflictingObjectsInDatabase"); //$NON-NLS-1$
                JTextArea conflictsPane = new JTextArea(cr.toConflictTree());
                conflictsPane.setRows(15);
                conflictsPane.setEditable(false);
                messages[1] = new JScrollPane(conflictsPane);
                messages[2] = Messages.getString("ExportDDLAction.dropConflictingObjectsConfirmation"); //$NON-NLS-1$
                int choice = JOptionPane.showConfirmDialog(parentDialog, messages,
                        Messages.getString("ExportDDLAction.conflictingObjectsInDatabaseDialogTitle"), //$NON-NLS-1$
                        JOptionPane.YES_NO_CANCEL_OPTION);
                if (choice == JOptionPane.YES_OPTION) {
                    shouldDropConflicts = true;
                } else if (choice == JOptionPane.NO_OPTION) {
                    shouldDropConflicts = false;
                } else if (choice == JOptionPane.CANCEL_OPTION) {
                    shouldDropConflicts = false;
                    this.setCancelled(true);
                }
            }

        }

        public ConflictResolver getConflictResolver() {
            return cr;
        }

        @Override
        protected Integer getJobSizeImpl() {
            return cr.getJobSize();
        }

        @Override
        protected String getMessageImpl() {
            return cr.getMessage();
        }

        @Override
        protected int getProgressImpl() {
            return cr.getProgress();
        }

        @Override
        protected boolean hasStartedImpl() {
            return cr.hasStarted();
        }

        @Override
        protected boolean isFinishedImpl() {
            return cr.isFinished();
        }

    }

    /**
     * The ConflictResolverProcess grabs a conflict resolver from the conflict
     * finder process, checks if the user said to delete the conflicts, then
     * asks it to remove the conflicting items while monitoring the progress.
     *
     * @author fuerth
     * @version $Id$
     */
    public class ConflictResolverProcess extends SPSwingWorker {

        private JDialog parentDialog;
        private ConflictFinderProcess conflictFinder;

        private ConflictResolver cr;

        private String errorMessage;
        private Exception error;

        /**
         * @param d The dialog we anchor popup messages to
         * @param cfp The conflict finder we extract the conflict list from
         * @param progressBar The progress bar we show our progress in
         * @param progressLabel The label where we say what we're doing
         */
        public ConflictResolverProcess(JDialog d, ConflictFinderProcess cfp, ArchitectSwingSession session) {
            super(session);
            this.parentDialog = d;
            this.conflictFinder = cfp;
        }

        public void doStuff() {
            if (isCancelled())
                return;
            if (conflictFinder.doesUserWantToDropConflicts()) {
                cr = conflictFinder.getConflictResolver();
                cr.aboutToCallDropConflicting();
                try {
                    cr.dropConflicting();
                } catch (Exception ex) {
                    logger.error("Error while dropping conflicting objects", ex); //$NON-NLS-1$
                    errorMessage = Messages.getString("ExportDDLAction.errorDroppingConflictingObjects") //$NON-NLS-1$
                            + ex.getMessage();
                }
            }
        }

        /**
         * 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() {
            if (errorMessage != null) {
                ASUtils.showExceptionDialogNoReport(parentDialog,
                        Messages.getString("ExportDDLAction.errorDroppingConflictingObjects") + errorMessage, //$NON-NLS-1$
                        error);
                setCancelled(true);
            }
        }

        @Override
        protected Integer getJobSizeImpl() {
            return cr.getJobSize();
        }

        @Override
        protected String getMessageImpl() {
            return cr.getMessage();
        }

        @Override
        protected int getProgressImpl() {
            return cr.getProgress();
        }

        @Override
        protected boolean hasStartedImpl() {
            return cr.hasStarted();
        }

        @Override
        protected boolean isFinishedImpl() {
            return cr.isFinished();
        }

    }

}