/*
* Copyright (c) 2008, 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;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import org.apache.log4j.Logger;
import ca.sqlpower.sql.DataSourceCollection;
import ca.sqlpower.sql.JDBCDataSource;
import ca.sqlpower.sql.JDBCDataSourceType;
import ca.sqlpower.sql.SPDataSource;
import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.layout.FormLayout;
public class JDBCDataSourcePanel implements DataEntryPanel {
private static final Logger logger = Logger.getLogger(JDBCDataSourcePanel.class);
protected static final String EXTRA_FIELD_LABEL_PROP = "ca.sqlpower.swingui.LABEL"; //$NON-NLS-1$
/**
* The panel that holds the GUI. This panel is built only once,
* on the first call to getPanel(). It is not initialized in the
* constructor, because subclasses need a chance to call {@link #addExtraField(JComponent)}
* before the panel is built.
*/
private JPanel panel;
/**
* The data source we're editing.
*/
private final JDBCDataSource dbcs;
private JTextField dbNameField;
private JComboBox dataSourceTypeBox;
private PlatformSpecificConnectionOptionPanel platformSpecificOptions;
private JTextField dbUrlField;
private JTextField dbUserField;
private JPasswordField dbPassField;
private JButton dbTestConnection;
private JLabel dbTestResult;
/**
* Contains information on the DB connection and system properties.
*/
private JTextArea sysprops;
/**
* Extra data entry fields provided by subclasses. These fields will be
* added to the layout, but not used in any other way. It's up to subclasses
* to set up the component in the constructor, and also override the
* applyChanges method to ensure the new field values are captured. The
* label given to these fields will be the EXTRA_FIELD_LABEL_PROP
* client property. See {@link JComponent#putClientProperty(Object, Object)}.
*/
private List<JComponent> extraFields = new ArrayList<JComponent>();
/**
* Controls whether or not {@link #applyChanges()} will enforce the policy
* of requiring data sources to have unique names within their data source
* collection. You almost always want this to be true (which is the
* default), but in rare cases such as the "target database properties" in
* Power*Architect, which is actually letting you modify a copy of the
* original data source, this check needs to be disabled.
*/
private boolean enforcingUniqueName = true;
/**
* Remembers the given data source, but does not build the GUI. That
* gets done the first time getPanel() is called.
*
* @param ds The data source to edit. It is the only data source this instance
* will ever be able to edit.
*/
public JDBCDataSourcePanel(JDBCDataSource ds) {
this.dbcs = ds;
}
/**
* Builds and returns a Swing component that has all the general database
* settings (the ones that are always required no matter what you want to
* use this connection for).
*/
private JPanel buildGeneralPanel(JDBCDataSource dbcs) {
DataSourceCollection<SPDataSource> dsCollection = dbcs.getParentCollection();
List<JDBCDataSourceType> dataSourceTypes = dsCollection.getDataSourceTypes();
dataSourceTypes.add(0, new JDBCDataSourceType());
dataSourceTypeBox = new JComboBox(dataSourceTypes.toArray());
dataSourceTypeBox.setRenderer(new SPDataSourceTypeListCellRenderer());
dataSourceTypeBox.setSelectedIndex(0);
// if this data source has no parent, it is a root data source
if (dbcs.isParentSet()) {
logger.debug("A PARENT! setting selected item to: \"" + dbcs.getParentType() + "\""); //$NON-NLS-1$ //$NON-NLS-2$
dataSourceTypeBox.setSelectedItem(dbcs.getParentType());
} else {
logger.debug("NO PARENT! setting selected item to: \"" + dbcs + "\""); //$NON-NLS-1$ //$NON-NLS-2$
dataSourceTypeBox.setSelectedItem(dbcs);
}
dbNameField = new JTextField(dbcs.getName());
dbNameField.setName("dbNameField"); //$NON-NLS-1$
logger.debug("dbcs.getUrl() returns " + dbcs.getUrl()); //$NON-NLS-1$
dbUrlField = new JTextField(dbcs.getUrl());
platformSpecificOptions = new PlatformSpecificConnectionOptionPanel(dbUrlField);
if (dbcs.isParentSet()) {
platformSpecificOptions.setTemplate(dbcs.getParentType());
}
dbTestResult = new JLabel();
sysprops = new JTextArea();
sysprops.setBorder( null );
sysprops.setOpaque( false );
sysprops.setEditable( false );
sysprops.setFont(dbTestResult.getFont());
dbTestConnection = new JButton(new AbstractAction(Messages.getString("SPDataSourcePanel.testConnectionActionName")) { //$NON-NLS-1$
public void actionPerformed(ActionEvent e) {
sysprops.setText("");
Connection con = null;
try {
JDBCDataSource dbcs = new JDBCDataSource(JDBCDataSourcePanel.this.dbcs.getParentCollection());
String name = dbNameField.getText();
dbcs.setName(name);
dbcs.setDisplayName(name);
dbcs.setParentType((JDBCDataSourceType) dataSourceTypeBox.getSelectedItem());
dbcs.setUrl(dbUrlField.getText());
dbcs.setUser(dbUserField.getText());
dbcs.setPass(new String(dbPassField.getPassword()));
con = dbcs.createConnection();
// No exception thrown, so success!
dbTestResult.setText(Messages.getString("SPDataSourcePanel.connectionTestSuccessful")); //$NON-NLS-1$
sysprops.append(JDBCDataSource.getConnectionInfoString(dbcs, true));
} catch (SQLException ex) {
dbTestResult.setText(Messages.getString("SPDataSourcePanel.connectionTestFailed")); //$NON-NLS-1$
SPSUtils.showExceptionDialogNoReport(panel, Messages.getString("SPDataSourcePanel.connectionTestException"), ex); //$NON-NLS-1$
} finally {
if (con != null) {
try {
con.close();
} catch (SQLException ex) {
logger.error("Failed to close connection!", ex); //$NON-NLS-1$
}
}
}
}
});
//we know this should be set to pref but one of the components seems to be updating the
//preferred size
DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("pref, 4dlu, 0:grow")); //$NON-NLS-1$
builder.append(Messages.getString("SPDataSourcePanel.connectionNameLabel"), dbNameField); //$NON-NLS-1$
builder.append(Messages.getString("SPDataSourcePanel.databaseTypeLabel"), dataSourceTypeBox); //$NON-NLS-1$
builder.append(Messages.getString("SPDataSourcePanel.connectionOptionsLabel"), platformSpecificOptions.getPanel()); //$NON-NLS-1$
builder.append(Messages.getString("SPDataSourcePanel.jdbcUrlLabel"), dbUrlField); //$NON-NLS-1$
builder.append(Messages.getString("SPDataSourcePanel.usernameLabel"), dbUserField = new JTextField(dbcs.getUser())); //$NON-NLS-1$
builder.append(Messages.getString("SPDataSourcePanel.passwordLabel"), dbPassField = new JPasswordField(dbcs.getPass())); //$NON-NLS-1$
// extra fields supplied by subclasses
for (JComponent extraField : extraFields) {
builder.append((String) extraField.getClientProperty(EXTRA_FIELD_LABEL_PROP), extraField);
}
builder.append(dbTestConnection, dbTestResult);
builder.append("\t\t", sysprops );
dataSourceTypeBox.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
JDBCDataSourceType parentType =
(JDBCDataSourceType) dataSourceTypeBox.getSelectedItem();
platformSpecificOptions.setTemplate(parentType);
}
});
// ensure enough width for the platform specific options
JPanel p = builder.getPanel();
p.setPreferredSize(new Dimension(600, 300));
return p;
}
/**
* Provides access to the combo box of data source types in this panel.
* Some outside classes that need to collaborate with this panel need
* to know when the user has selected a different data source type,
* and if you've got one, you can use this method to get the combo box
* and add an ItemListener to it.
*/
public JComboBox getDataSourceTypeBox() {
return dataSourceTypeBox;
}
/**
* Returns a reference to the data source this panel is editing (that is,
* the one that will be updated when apply() is called).
*/
public JDBCDataSource getDbcs() {
return dbcs;
}
/**
* Adds an extra data entry field which will be laid out after the
* rest of the components in this panel. The component must have
* its EXTRA_FIELD_LABEL_PROP client property set to the label
* you want the field to have.
* <p>
* You can call this method as many times as you want, but only before
* the first call to {@link #getPanel()}. After that, it is an error
* to call this method.
*
* @param component The component to add.
*/
protected void addExtraField(JComponent component) {
if (panel != null) throw new IllegalStateException("You can't do this after calling getPanel()"); //$NON-NLS-1$
extraFields.add(component);
}
// -------------------- DATE ENTRY PANEL INTERFACE -----------------------
/**
* Copies the properties displayed in the various fields back into
* the current SPDataSource. You still need to call getDbcs()
* and save the connection spec yourself.
*/
public boolean applyChanges() {
dbNameField.setText(dbNameField.getText().trim());
if ("".equals(dbNameField.getText())) { //$NON-NLS-1$
JOptionPane.showMessageDialog(panel,
Messages.getString("SPDataSourcePanel.blankNameNotAllowed")); //$NON-NLS-1$
return false;
}
SPDataSource existingDSWithThisName = dbcs.getParentCollection().getDataSource(dbNameField.getText());
if (enforcingUniqueName && existingDSWithThisName != null && existingDSWithThisName != dbcs) {
JOptionPane.showMessageDialog(panel, Messages.getString("SPDataSourcePanel.connectionAlreadyExists", dbNameField.getText())); //$NON-NLS-1$
return false;
}
logger.debug("Applying changes..."); //$NON-NLS-1$
String name = dbNameField.getText();
dbcs.setName(name);
dbcs.setDisplayName(name);
dbcs.setParentType((JDBCDataSourceType) dataSourceTypeBox.getSelectedItem());
dbcs.setUrl(dbUrlField.getText());
dbcs.setUser(dbUserField.getText());
dbcs.setPass(new String(dbPassField.getPassword())); // completely defeats the purpose for JPasswordField.getText() being deprecated, but we're saving passwords to the config file so it hardly matters.
return true;
}
/**
* Does nothing right now, because there is nothing to discard or clean up.
*/
public void discardChanges() {
// nothing to discard
}
/**
* Returns the panel that holds the user interface for the datatbase
* connection settings.
*/
public JPanel getPanel() {
if (panel == null) {
panel = buildGeneralPanel(dbcs);
}
return panel;
}
public boolean hasUnsavedChanges() {
//TODO: tell the truth
return true;
}
/**
* Controls whether or not {@link #applyChanges()} will enforce the policy
* of requiring data sources to have unique names within their data source
* collection. You almost always want this to be true (which is the
* default), but in rare cases such as the "target database properties" in
* Power*Architect, which is actually letting you modify a copy of the
* original data source, this check needs to be disabled.
*/
public void setEnforcingUniqueName(boolean enforceUniqueName) {
this.enforcingUniqueName = enforceUniqueName;
}
/**
* Returns the flag indicating whether or not {@link #applyChanges()} will
* insist that the data source's new name is unique.
*/
public boolean isEnforcingUniqueName() {
return enforcingUniqueName;
}
}