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

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2009, 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.Component;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.util.HashSet;
import java.util.Set;

import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreePath;

import ca.sqlpower.architect.swingui.ASUtils;
import ca.sqlpower.architect.swingui.ArchitectSwingSession;
import ca.sqlpower.architect.swingui.DBTree;
import ca.sqlpower.architect.swingui.dbtree.DBTreeModel;
import ca.sqlpower.sqlobject.SQLDatabase;
import ca.sqlpower.sqlobject.SQLObject;
import ca.sqlpower.sqlobject.SQLObjectException;
import ca.sqlpower.swingui.ProgressWatcher;
import ca.sqlpower.swingui.SPSwingWorker;
import ca.sqlpower.swingui.SwingWorkerRegistry;
import ca.sqlpower.swingui.event.TaskTerminationEvent;
import ca.sqlpower.swingui.event.TaskTerminationListener;
import ca.sqlpower.util.SQLPowerUtils;

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

public class RefreshAction extends AbstractArchitectAction {

    private DBTree dbTree;

    /**
     * This will refresh all of the databases on a different thread and
     * allows the refresh to be monitorable.
     */
    private class RefreshMonitorableWorker extends SPSwingWorker {

        private final Set<SQLDatabase> databasesToRefresh = new HashSet<SQLDatabase>();
        private final Component parent;
        private SQLDatabase dbBeingRefreshed;

        public RefreshMonitorableWorker(SwingWorkerRegistry registry, Component parent, Set<SQLDatabase> dbs) {
            super(registry);
            this.parent = parent;
            this.databasesToRefresh.addAll(dbs);
            setJobSize(null);
        }

        @Override
        protected String getMessageImpl() {
            if (dbBeingRefreshed == null) {
                return "Refreshing selected databases.";
            }
            return "Refreshing database " + dbBeingRefreshed.getName();
        }

        @Override
        public void doStuff() throws Exception {
            setProgress(0);
            try {
                for (SQLDatabase db : databasesToRefresh) {
                    dbBeingRefreshed = db;
                    db.refresh();
                    increaseProgress();
                }
            } catch (SQLObjectException ex) {
                setDoStuffException(ex);
            } finally {
                dbBeingRefreshed = null;
            }
        }

        @Override
        public void cleanup() throws Exception {
            // XXX this is not ideal because it collapses all tree nodes after the refresh is done.
            // However, it is necessary to notify the DBTree (JTree) of the changes, because all the
            // events that happened during the refresh were not on the Event Dispatch Thread,
            // and DBTreeModel correctly filtered them out. At this point, it's too late to feed the
            // piecewise updates to the JTree because they describe interim state that has already
            // come and gone.
            //
            // I believe the best fix for this is to factor out the refresh logic into a separate
            // class (SQLDatabaseRefresher?) which can be instantiated for any database. It would
            // walk the tree and do the refresh work, but know enough to perform the actual tree
            // manipulations on the EDT.
            //
            // Another possibility to look into would be to improve the SQLObject event mechanism
            // with enough locking that it would be possible to use SwingUtilities.invokeAndWait()
            // to refire the events in the DBTreeModel. The behaviour would be that other threads
            // trying to mutate anything in the tree which is currently busy firing an event would
            // block until the event has been delivered to all its listeners. This could probably
            // deadlock too easily though, since the EDT might already have something in the queue
            // that would block until the invokeAndWait item has run.
            ((DBTreeModel) getSession().getDBTree().getModel()).refreshTreeStructure();

            if (getDoStuffException() != null) {
                ASUtils.showExceptionDialogNoReport(parent, "Refresh failed", getDoStuffException());
            }
        }
    }

    public RefreshAction(ArchitectSwingSession session) {
        super(session, "Refresh", "Refreshes the tree to match current structure in the selected database",
                new ImageIcon(RefreshAction.class.getResource("/icons/database_refresh.png")));
    }

    /** 
     * Refreshes based on selections in a specific tree.
     */
    public RefreshAction(ArchitectSwingSession session, DBTree tree) {
        super(session, "Refresh", "Refreshes the tree to match current structure in the selected database",
                new ImageIcon(RefreshAction.class.getResource("/icons/database_refresh.png")));
        dbTree = tree;
    }

    public void actionPerformed(ActionEvent e) {
        if (dbTree == null) {
            dbTree = getSession().getDBTree();
        }

        Set<SQLDatabase> databasesToRefresh = new HashSet<SQLDatabase>();
        for (TreePath tp : dbTree.getSelectionPaths()) {
            SQLObject so = (SQLObject) tp.getLastPathComponent();
            SQLDatabase db = SQLPowerUtils.getAncestor(so, SQLDatabase.class);
            if (db != null && !db.isPlayPenDatabase()) {
                databasesToRefresh.add(db);
            }
        }

        if (databasesToRefresh.isEmpty()) {
            JOptionPane.showMessageDialog(dbTree, "Please select a source database to refresh");
            return;
        }

        final SPSwingWorker worker = new RefreshMonitorableWorker(getSession(),
                (Window) SwingUtilities.getRoot(dbTree), databasesToRefresh);
        final Thread thread = new Thread(worker, "Refresh database worker");
        JProgressBar progressBar = new JProgressBar();
        progressBar.setIndeterminate(true);
        JLabel messageLabel = new JLabel("Refreshing selected databases.");
        ProgressWatcher.watchProgress(progressBar, worker, messageLabel);

        final JDialog dialog = new JDialog((Window) SwingUtilities.getRoot(dbTree), "Refresh");
        DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("pref:grow, 5dlu, pref"));
        builder.setDefaultDialogBorder();
        builder.append(messageLabel, 3);
        builder.nextLine();
        builder.append(progressBar, 3);
        builder.nextLine();
        builder.append(new JLabel(""), new JButton(new AbstractAction("Cancel") {
            public void actionPerformed(ActionEvent e) {
                worker.kill();
            }
        }));
        dialog.add(builder.getPanel());

        worker.addTaskTerminationListener(new TaskTerminationListener() {
            public void taskFinished(TaskTerminationEvent e) {
                dialog.dispose();
            }
        });

        dialog.pack();
        dialog.setModal(true);
        dialog.setLocationRelativeTo(getSession().getArchitectFrame());
        thread.start();
        dialog.setVisible(true);

    }

}