Java tutorial
/* * 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.Cursor; import java.awt.Window; import java.awt.event.ActionEvent; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.TreePath; import ca.sqlpower.architect.ArchitectProject; import ca.sqlpower.architect.DepthFirstSearch; import ca.sqlpower.architect.ddl.DDLGenerator; import ca.sqlpower.architect.ddl.DDLStatement; import ca.sqlpower.architect.ddl.DDLUtils; import ca.sqlpower.architect.swingui.action.DatabaseConnectionManagerAction; import ca.sqlpower.architect.swingui.dbtree.DBTreeCellRenderer; import ca.sqlpower.architect.swingui.dbtree.DBTreeModel; import ca.sqlpower.object.ObjectDependentException; import ca.sqlpower.sql.DataMover; import ca.sqlpower.sql.DatabaseListChangeEvent; import ca.sqlpower.sql.DatabaseListChangeListener; import ca.sqlpower.sql.JDBCDataSource; import ca.sqlpower.sqlobject.SQLCatalog; import ca.sqlpower.sqlobject.SQLColumn; import ca.sqlpower.sqlobject.SQLDatabase; import ca.sqlpower.sqlobject.SQLObject; import ca.sqlpower.sqlobject.SQLObjectException; import ca.sqlpower.sqlobject.SQLObjectRoot; import ca.sqlpower.sqlobject.SQLObjectUtils; import ca.sqlpower.sqlobject.SQLSchema; import ca.sqlpower.sqlobject.SQLTable; import ca.sqlpower.swingui.SPSUtils; import ca.sqlpower.util.SQLPowerUtils; import com.jgoodies.forms.builder.PanelBuilder; import com.jgoodies.forms.factories.ButtonBarFactory; import com.jgoodies.forms.layout.CellConstraints; import com.jgoodies.forms.layout.FormLayout; public class DataMoverPanel { /** * The panel that holds the UI. */ private JPanel panel; /** * Tree of source objects that you might want to copy. */ private JTree sourceTree; /** * Tree of target objects that you can copy into. */ private JTree destTree; /** * The root object of the source and destination trees. */ private SQLObjectRoot treeRoot; /** * Checkbox for selecting whether or not to wipe out the destination * table's contents before loading. */ private JCheckBox truncateDestinationTableBox; /** * The session that this Data Mover belongs to. */ private ArchitectSwingSession session; /** * A {@link TreeSelectionListener} that listens to the {@link #sourceTree} * and {@link #destTree} in order to verify a valid selection before * proceeding to copy table data. This listener should be added to both * these trees once they are created. */ private final TreeSelectionListener dbTreeListener = new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { boolean buttonEnabled = okAction.isEnabled(); boolean sourceValid = isSourceTreeSelectionValid(); boolean destValid = isDestTreeSelectionValid(); if (!buttonEnabled && sourceValid && destValid) { okAction.setEnabled(true); } else if (buttonEnabled && (!sourceValid || !destValid)) { okAction.setEnabled(false); } } }; public DataMoverPanel(ArchitectSwingSession session) throws SQLObjectException { this.session = session; setupDBTrees(); okAction.setEnabled(false); sourceTree = new JTree(); final DBTreeModel sourceTreeModel = new DBTreeModel(treeRoot, sourceTree); sourceTree.setModel(sourceTreeModel); sourceTree.setRootVisible(false); sourceTree.setShowsRootHandles(true); sourceTree.setCellRenderer(new DBTreeCellRenderer()); sourceTree.addTreeSelectionListener(dbTreeListener); destTree = new JTree(); final DBTreeModel destTreeModel = new DBTreeModel(treeRoot, sourceTree); destTree.setModel(destTreeModel); destTree.setRootVisible(false); destTree.setShowsRootHandles(true); destTree.setCellRenderer(new DBTreeCellRenderer()); destTree.addTreeSelectionListener(dbTreeListener); PanelBuilder pb = new PanelBuilder(new FormLayout("pref:grow,8dlu,pref:grow", //$NON-NLS-1$ "pref,4dlu,fill:pref:grow,4dlu,pref,4dlu,pref")); //$NON-NLS-1$ pb.getLayout().addGroupedColumn(1); pb.getLayout().addGroupedColumn(3); CellConstraints cc = new CellConstraints(); pb.addLabel(Messages.getString("DataMoverPanel.sourceLabel"), cc.xy(1, 1)); //$NON-NLS-1$ pb.addLabel(Messages.getString("DataMoverPanel.destinationLabel"), cc.xy(3, 1)); //$NON-NLS-1$ pb.add(new JScrollPane(sourceTree), cc.xy(1, 3)); pb.add(new JScrollPane(destTree), cc.xy(3, 3)); session.getDataSources().addDatabaseListChangeListener(new DatabaseListChangeListener() { public void databaseAdded(DatabaseListChangeEvent e) { try { setupDBTrees(); } catch (SQLObjectException ex) { SPSUtils.showExceptionDialogNoReport(panel, Messages.getString("DataMoverPanel.couldNotFindDB"), //$NON-NLS-1$ ex); } } public void databaseRemoved(DatabaseListChangeEvent e) { try { setupDBTrees(); } catch (SQLObjectException ex) { SPSUtils.showExceptionDialogNoReport(panel, Messages.getString("DataMoverPanel.couldNotFindDB"), //$NON-NLS-1$ ex); } } }); pb.add(new JButton(new DatabaseConnectionManagerAction(session)), cc.xy(1, 5)); pb.add(truncateDestinationTableBox = new JCheckBox( Messages.getString("DataMoverPanel.truncateDestinationTableOption")), cc.xy(3, 5)); //$NON-NLS-1$ pb.add(ButtonBarFactory.buildOKCancelBar(new JButton(okAction), new JButton(cancelAction)), cc.xyw(1, 7, 3)); pb.setDefaultDialogBorder(); panel = pb.getPanel(); } /** * Sets the trees in the data mover panel to have all of the connections * in the current context. */ private void setupDBTrees() throws SQLObjectException { try { if (treeRoot == null) { treeRoot = new SQLObjectRoot(); ArchitectProject treeProject = new ArchitectProject(treeRoot); treeProject.setSession(session); treeRoot.begin("Setting up database trees in data mover panel."); } else { treeRoot.begin("Setting up database trees in data mover panel."); for (int i = treeRoot.getChildCount() - 1; i >= 0; i--) { treeRoot.removeChild(treeRoot.getChild(i)); } } for (JDBCDataSource ds : session.getDataSources().getConnections()) { treeRoot.addChild(new SQLDatabase(ds)); } treeRoot.commit(); } catch (IllegalArgumentException e) { treeRoot.rollback("Could not set up database trees."); throw new RuntimeException(e); } catch (ObjectDependentException e) { treeRoot.rollback("Could not set up database trees."); throw new RuntimeException(e); } } private Action okAction = new AbstractAction(Messages.getString("DataMoverPanel.okButton")) { //$NON-NLS-1$ public void actionPerformed(ActionEvent e) { try { panel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); doDataMove(); } catch (Exception ex) { ASUtils.showExceptionDialog(session, Messages.getString("DataMoverPanel.failedToMoveData"), ex); //$NON-NLS-1$ } finally { panel.setCursor(null); } } }; private Action cancelAction = new AbstractAction(Messages.getString("DataMoverPanel.cancelButton")) { //$NON-NLS-1$ public void actionPerformed(ActionEvent e) { try { Window w = SPSUtils.getWindowInHierarchy(panel); if (w != null) w.dispose(); } catch (Exception ex) { ASUtils.showExceptionDialog(session, Messages.getString("DataMoverPanel.failedToMoveData"), ex); //$NON-NLS-1$ } } }; public JPanel getPanel() { return panel; } public void doDataMove() throws SQLException, SQLObjectException { final TreePath[] sourcePaths = sourceTree.getSelectionPaths(); int tableCount = 0; int rowCount = 0; List<SQLTable> sourceTables = new ArrayList<SQLTable>(); for (TreePath sourcePath : sourcePaths) { sourceTables.add((SQLTable) sourcePath.getLastPathComponent()); } DepthFirstSearch dfs = new DepthFirstSearch(sourceTables); for (SQLTable sourceTable : dfs.getFinishOrder()) { int thisCount = moveSingleTable(sourceTable); if (thisCount == -1) { int choice = JOptionPane.showConfirmDialog(panel, Messages.getString("DataMoverPanel.continueCopyingRemainingTablesOption")); //$NON-NLS-1$ if (choice != JOptionPane.YES_OPTION) { break; } } else { tableCount += 1; rowCount += thisCount; } } JOptionPane.showMessageDialog(panel, Messages.getString("DataMoverPanel.dataCopyResults", //$NON-NLS-1$ String.valueOf(tableCount), String.valueOf(rowCount))); } /** * Moves the data from the table identified by * @param sourcePath * @return The number of rows moved, or -1 if the user cancelled the operation. * @throws SQLException * @throws SQLObjectException */ private int moveSingleTable(final SQLTable sourceTable) throws SQLException, SQLObjectException { final SQLDatabase sourceDB = SQLPowerUtils.getAncestor(sourceTable, SQLDatabase.class); final TreePath destPath = destTree.getSelectionPath(); final SQLObject destObject = (SQLObject) destPath.getLastPathComponent(); final SQLDatabase destDB = SQLPowerUtils.getAncestor(destObject, SQLDatabase.class); String destCatalogName = null; String destSchemaName = null; String destTableName = sourceTable.getName(); List<SQLObject> ancestorList = SQLObjectUtils.ancestorList(destObject); for (SQLObject ancestor : ancestorList) { // walk up the ancestors and set table, catalog, and schema name as appropriate if (ancestor instanceof SQLTable) destTableName = ancestor.getName(); if (ancestor instanceof SQLCatalog) destCatalogName = ancestor.getName(); if (ancestor instanceof SQLSchema) destSchemaName = ancestor.getName(); } boolean needToCreate = false; SQLTable destTable = destDB.getTableByName(destCatalogName, destSchemaName, destTableName); if (destTable == null) { needToCreate = true; destTable = SQLObjectUtils.addSimulatedTable(destDB, destCatalogName, destSchemaName, destTableName); // setup columns for (SQLColumn srcCol : sourceTable.getColumns()) { destTable.addColumn(new SQLColumn(srcCol)); } // setup PK for (SQLColumn srcCol : sourceTable.getColumns()) { SQLColumn destCol = destTable.getColumnByName(srcCol.getName()); destTable.getPrimaryKeyIndex().addIndexColumn(destCol); } // TODO indexes and foreign keys } Connection sourceCon = null; Connection destCon = null; try { sourceCon = sourceDB.getConnection(); destCon = destDB.getConnection(); String sourceQuoteString = sourceCon.getMetaData().getIdentifierQuoteString(); String destQuoteString = destCon.getMetaData().getIdentifierQuoteString(); final String sourceQualifiedName = DDLUtils.toQualifiedName(sourceTable.getCatalogName(), sourceTable.getSchemaName(), sourceTable.getName(), sourceQuoteString, sourceQuoteString); final String destQualifiedName = DDLUtils.toQualifiedName(destTable.getCatalogName(), destTable.getSchemaName(), destTable.getName(), destQuoteString, destQuoteString); if (needToCreate) { if (destTable.getColumns().isEmpty()) { int choice = JOptionPane.showOptionDialog(panel, Messages.getString("DataMoverPanel.sourceTableHasNoColumns", sourceQualifiedName), "Unsupported Source Table Structure", JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, new String[] { "Skip", "Abort" }, "Skip"); if (choice == 0) { return 0; // copied 0 rows } else { return -1; // abort } } else { int choice = JOptionPane.showConfirmDialog(panel, Messages.getString("DataMoverPanel.destinationTableDoesNotExist", destQualifiedName)); if (choice != JOptionPane.YES_OPTION) return -1; } // check for common problems for (SQLColumn destCol : destTable.getColumns()) { if (destCol.getDefaultValue() != null) { int choice = JOptionPane.showOptionDialog(panel, Messages.getString("DataMoverPanel.sourceColumnHasDefault", sourceQualifiedName + "." + destCol.getName(), destCol.getDefaultValue()), "Source Table Structure Question", JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, new String[] { "Retain Default", "Change Default To Null" }, "Skip"); if (choice == 1) { destCol.setDefaultValue(null); } } } DDLGenerator ddlg = DDLUtils.createDDLGenerator(destDB.getDataSource()); ddlg.generateDDLStatements(Collections.singletonList(destTable)); Statement stmt = null; String sql = null; try { stmt = destCon.createStatement(); for (DDLStatement ddlstmt : ddlg.getDdlStatements()) { sql = ddlstmt.getSQLText(); stmt.executeUpdate(sql); } } catch (SQLException ex) { throw new RuntimeException("DDL statement failed: " + sql, ex); } finally { if (stmt != null) stmt.close(); } } DataMover mover = new DataMover(destCon, sourceCon); mover.setCreatingDestinationTable(false); mover.setTruncatingDestinationTable(truncateDestinationTableBox.isSelected()); mover.setDebug(true); // when true, debug data goes to System.out // JOptionPane.showMessageDialog(panel, // "About to copy\n"+sourceQualifiedName+"\nto\n"+destQualifiedName); int count = mover.copyTable(destQualifiedName, sourceQualifiedName); return count; } catch (InstantiationException ex) { throw new RuntimeException("Couldn't create DDL Generator", ex); //$NON-NLS-1$ } catch (IllegalAccessException ex) { throw new RuntimeException("Couldn't create DDL Generator", ex); //$NON-NLS-1$ } catch (ClassNotFoundException ex) { throw new RuntimeException("Couldn't create DDL Generator", ex); //$NON-NLS-1$ } finally { sourceCon.close(); destCon.close(); } } /** * Returns true if the source DB Tree selections are valid for the copy * table data process. That is, everything that is selected in the source * tree must be a SQLTable. */ private boolean isSourceTreeSelectionValid() { if (sourceTree.isSelectionEmpty()) { return false; } else { for (TreePath path : sourceTree.getSelectionPaths()) { if (!(path.getLastPathComponent() instanceof SQLTable)) { return false; } } return true; } } /** * Returns true if the destination DB Tree selection is valid for the copy * table data process. That is, there must be at least one item in the * destination tree that is selected. */ private boolean isDestTreeSelectionValid() { return !destTree.isSelectionEmpty(); } }