All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.datacleaner.windows.JdbcDatastoreDialog Maven / Gradle / Ivy

/**
 * DataCleaner (community edition)
 * Copyright (C) 2014 Free Software Foundation, Inc.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program 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 Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.datacleaner.windows;

import java.awt.BorderLayout;
import java.awt.Component;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Provider;
import javax.sql.DataSource;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JSeparator;
import javax.swing.event.DocumentEvent;

import org.apache.metamodel.util.FileHelper;
import org.datacleaner.bootstrap.WindowContext;
import org.datacleaner.connection.JdbcDatastore;
import org.datacleaner.database.DatabaseDescriptorImpl;
import org.datacleaner.database.DatabaseDriverCatalog;
import org.datacleaner.database.DatabaseDriverDescriptor;
import org.datacleaner.guice.Nullable;
import org.datacleaner.panels.DCPanel;
import org.datacleaner.user.MutableDatastoreCatalog;
import org.datacleaner.user.UserPreferences;
import org.datacleaner.util.DCDocumentListener;
import org.datacleaner.util.IconUtils;
import org.datacleaner.util.ImageManager;
import org.datacleaner.util.StringUtils;
import org.datacleaner.util.WidgetFactory;
import org.datacleaner.util.WidgetUtils;
import org.datacleaner.widgets.DCCheckBox;
import org.datacleaner.widgets.DCComboBox;
import org.datacleaner.widgets.DCLabel;
import org.datacleaner.widgets.DCListCellRenderer;
import org.datacleaner.widgets.DescriptionLabel;
import org.datacleaner.widgets.database.CubridDatabaseConnectionPresenter;
import org.datacleaner.widgets.database.DatabaseConnectionPresenter;
import org.datacleaner.widgets.database.DefaultDatabaseConnectionPresenter;
import org.datacleaner.widgets.database.H2DatabaseConnectionPresenter;
import org.datacleaner.widgets.database.HiveDatabaseConnectionPresenter;
import org.datacleaner.widgets.database.MysqlDatabaseConnectionPresenter;
import org.datacleaner.widgets.database.OracleDatabaseConnectionPresenter;
import org.datacleaner.widgets.database.PostgresqlDatabaseConnectionPresenter;
import org.datacleaner.widgets.database.SQLServerDatabaseConnectionPresenter;
import org.datacleaner.widgets.tabs.CloseableTabbedPane;
import org.jdesktop.swingx.JXTextField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JdbcDatastoreDialog extends AbstractDatastoreDialog {

    public static final int TEXT_FIELD_WIDTH = 30;
    private static final long serialVersionUID = 1L;
    private static final Logger logger = LoggerFactory.getLogger(JdbcDatastoreDialog.class);
    /**
     * Number of connections to try to create (in case of non-multiple
     * connections, this is just the number of handles to the same connection).
     */
    private static final int TEST_CONNECTION_COUNT = 4;

    private static final String MANAGE_DATABASE_DRIVERS = "Manage database drivers...";
    private static final ImageManager imageManager = ImageManager.get();

    private final DatabaseDriverCatalog _databaseDriverCatalog;
    private final JXTextField _driverClassNameTextField;
    private final DCCheckBox _multipleConnectionsCheckBox;
    private final DCComboBox _databaseDriverComboBox;
    private final Provider _optionsDialogProvider;
    private final CloseableTabbedPane _tabbedPane;
    private final DatabaseConnectionPresenter[] _connectionPresenters;

    @Inject
    protected JdbcDatastoreDialog(@Nullable final JdbcDatastore originalDatastore,
            final MutableDatastoreCatalog catalog, final WindowContext windowContext,
            final Provider optionsDialogProvider, final DatabaseDriverCatalog databaseDriverCatalog,
            final UserPreferences userPreferences) {
        super(originalDatastore, catalog, windowContext, userPreferences);

        setSaveButtonEnabled(false);

        _optionsDialogProvider = optionsDialogProvider;
        _databaseDriverCatalog = databaseDriverCatalog;

        // there will always be 2 connection presenters, but the second is
        // optional
        _connectionPresenters = new DatabaseConnectionPresenter[2];
        _connectionPresenters[0] = new DefaultDatabaseConnectionPresenter();

        _tabbedPane = new CloseableTabbedPane(true);
        _tabbedPane.addTab("Generic connection parameters",
                imageManager.getImageIcon(IconUtils.GENERIC_DATASTORE_IMAGEPATH, IconUtils.ICON_SIZE_MEDIUM),
                _connectionPresenters[0].getWidget());
        _tabbedPane.setUnclosableTab(0);

        _multipleConnectionsCheckBox = new DCCheckBox<>("Allow multiple concurrent connections", true);
        _multipleConnectionsCheckBox.setToolTipText(
                "Indicates whether multiple connections (aka. connection pooling) may be created or not. "
                        + "Connection pooling is preferred for performance reasons, but can safely be disabled if not desired. "
                        + "The max number of connections cannot be configured, "
                        + "but no more connections than the number of threads in the task runner should be expected.");
        _multipleConnectionsCheckBox.setOpaque(false);
        _multipleConnectionsCheckBox.setForeground(WidgetUtils.BG_COLOR_BRIGHTEST);

        _driverClassNameTextField = WidgetFactory.createTextField("Driver class name", TEXT_FIELD_WIDTH);

        final List databaseDrivers =
                _databaseDriverCatalog.getInstalledWorkingDatabaseDrivers();
        final Object[] comboBoxModel = new Object[databaseDrivers.size() + 3];
        comboBoxModel[0] = "";
        for (int i = 0; i < databaseDrivers.size(); i++) {
            comboBoxModel[i + 1] = databaseDrivers.get(i);
        }
        comboBoxModel[comboBoxModel.length - 2] = new JSeparator(JSeparator.HORIZONTAL);
        comboBoxModel[comboBoxModel.length - 1] = MANAGE_DATABASE_DRIVERS;

        _databaseDriverComboBox = new DCComboBox<>(comboBoxModel);
        _databaseDriverComboBox.setRenderer(new DCListCellRenderer() {

            private static final long serialVersionUID = 1L;

            @Override
            public Component getListCellRendererComponent(final JList list, Object value, final int index,
                    final boolean isSelected, final boolean cellHasFocus) {
                if ("".equals(value)) {
                    value = "- select -";
                }

                final JLabel result =
                        (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);

                if (value instanceof DatabaseDriverDescriptor) {
                    final DatabaseDriverDescriptor databaseDriver = (DatabaseDriverDescriptor) value;

                    final String iconImagePath = DatabaseDriverCatalog.getIconImagePath(databaseDriver);
                    final Icon driverIcon = imageManager.getImageIcon(iconImagePath, IconUtils.ICON_SIZE_SMALL);

                    result.setText(databaseDriver.getDisplayName());
                    result.setIcon(driverIcon);
                } else if (MANAGE_DATABASE_DRIVERS.equals(value)) {
                    result.setIcon(imageManager.getImageIcon(IconUtils.MENU_OPTIONS, IconUtils.ICON_SIZE_SMALL));
                } else if (value instanceof Component) {
                    return (Component) value;
                }

                return result;
            }
        });

        _databaseDriverComboBox.addListener(value -> {
            if ("".equals(value)) {
                setSelectedDatabase((DatabaseDriverDescriptor) null);
                _driverClassNameTextField.setText("");
                _driverClassNameTextField.setEnabled(true);
            } else if (value instanceof DatabaseDriverDescriptor) {
                final DatabaseDriverDescriptor driver = (DatabaseDriverDescriptor) value;

                setSelectedDatabase(driver);

                _driverClassNameTextField.setText(driver.getDriverClassName());
                _driverClassNameTextField.setEnabled(false);
            } else if (MANAGE_DATABASE_DRIVERS.equals(value)) {
                final OptionsDialog optionsDialog = _optionsDialogProvider.get();
                optionsDialog.selectDatabaseDriversTab();
                JdbcDatastoreDialog.this.dispose();

                optionsDialog.setVisible(true);
                optionsDialog.toFront();
            }
        });

        if (originalDatastore == null) {
            // remove connection url templates
            setSelectedDatabase((DatabaseDriverDescriptor) null);
        } else {
            _multipleConnectionsCheckBox.setSelected(originalDatastore.isMultipleConnections());

            // the database driver has to be set as the first thing, because the
            // combobox's action listener will set other field's values as well.
            final DatabaseDriverDescriptor databaseDriver =
                    DatabaseDriverCatalog.getDatabaseDriverByDriverClassName(originalDatastore.getDriverClass());
            _databaseDriverComboBox.setSelectedItem(databaseDriver);

            _datastoreNameTextField.setText(originalDatastore.getName());
            _datastoreNameTextField.setEnabled(false);

            _connectionPresenters[0].initialize(originalDatastore);

            _driverClassNameTextField.setText(originalDatastore.getDriverClass());
        }

        _databaseDriverComboBox.addListener(item -> validateAndUpdate());

        _driverClassNameTextField.getDocument().addDocumentListener(new DCDocumentListener() {
            @Override
            protected void onChange(final DocumentEvent event) {
                validateAndUpdate();
            }
        });

    }

    protected boolean validateForm() {
        final String datastoreName = _datastoreNameTextField.getText();
        if (StringUtils.isNullOrEmpty(datastoreName)) {
            setStatusError("Please enter a datastore name");
            return false;
        }

        final DatabaseDriverDescriptor databaseDriverDescriptor =
                (DatabaseDescriptorImpl) _databaseDriverComboBox.getSelectedItem();
        if (databaseDriverDescriptor == null) {
            final String databaseDriverClass = _driverClassNameTextField.getText();
            if (StringUtils.isNullOrEmpty(databaseDriverClass)) {
                setStatusError("Please specify database driver class or choose one from the list");
                return false;
            }
        }

        setStatusValid();
        return true;
    }

    public void setSelectedDatabase(final String databaseName) {
        final DatabaseDriverDescriptor databaseDriverDescriptor =
                DatabaseDriverCatalog.getDatabaseDriverByDriverDatabaseName(databaseName);
        setSelectedDatabase(databaseDriverDescriptor);
    }

    public void setSelectedDatabase(final DatabaseDriverDescriptor databaseDriverDescriptor) {
        _databaseDriverComboBox.setSelectedItem(databaseDriverDescriptor);

        if (_tabbedPane.getTabCount() > 1) {
            _tabbedPane.removeTabAt(1);
        }

        // register the presenter
        final DatabaseConnectionPresenter customPresenter = createDatabaseConnectionPresenter(databaseDriverDescriptor);
        _connectionPresenters[1] = customPresenter;

        if (customPresenter != null) {
            boolean accepted = true;

            // init if original datastore is available
            if (getOriginalDatastore() != null) {
                accepted = customPresenter.initialize(getOriginalDatastore());
            }

            if (accepted) {

                _tabbedPane.setUnclosableTab(1);
                _tabbedPane.addTab(databaseDriverDescriptor.getDisplayName() + " connection", imageManager
                        .getImageIcon(databaseDriverDescriptor.getIconImagePath(), IconUtils.ICON_SIZE_MEDIUM,
                                customPresenter.getClass().getClassLoader()), customPresenter.getWidget());
                _tabbedPane.setSelectedIndex(1);
            } else {
                // unregister the presenter
                _connectionPresenters[1] = null;
            }
        }

        for (final DatabaseConnectionPresenter connectionPresenter : _connectionPresenters) {
            if (connectionPresenter != null) {
                connectionPresenter.setSelectedDatabaseDriver(databaseDriverDescriptor);
            }
        }
    }

    public DatabaseConnectionPresenter createDatabaseConnectionPresenter(
            final DatabaseDriverDescriptor databaseDriverDescriptor) {
        if (databaseDriverDescriptor == null) {
            return null;
        }
        final String databaseName = databaseDriverDescriptor.getDisplayName();

        final DatabaseConnectionPresenter result;

        switch (databaseName) {
        case DatabaseDriverCatalog.DATABASE_NAME_MYSQL:
            result = new MysqlDatabaseConnectionPresenter();
            break;
        case DatabaseDriverCatalog.DATABASE_NAME_POSTGRESQL:
            result = new PostgresqlDatabaseConnectionPresenter();
            break;
        case DatabaseDriverCatalog.DATABASE_NAME_ORACLE:
            result = new OracleDatabaseConnectionPresenter();
            break;
        case DatabaseDriverCatalog.DATABASE_NAME_MICROSOFT_SQL_SERVER_JTDS:
            result = new SQLServerDatabaseConnectionPresenter();
            break;
        case DatabaseDriverCatalog.DATABASE_NAME_CUBRID:
            result = new CubridDatabaseConnectionPresenter();
            break;
        case DatabaseDriverCatalog.DATABASE_NAME_H2:
            result = new H2DatabaseConnectionPresenter();
            break;
        case DatabaseDriverCatalog.DATABASE_NAME_HIVE:
            result = new HiveDatabaseConnectionPresenter();
            break;
        default:
            result = null;
        }

        return result;
    }

    @Override
    protected boolean isWindowResizable() {
        return true;
    }

    @Override
    protected String getBannerTitle() {
        return "Database connection";
    }

    @Override
    protected JComponent getDialogContent() {
        final DCPanel formPanel = new DCPanel();
        {
            int row = 0;
            WidgetUtils.addToGridBag(DCLabel.bright("Datastore name:"), formPanel, 0, row);
            WidgetUtils.addToGridBag(_datastoreNameTextField, formPanel, 1, row);

            row++;
            WidgetUtils.addToGridBag(DCLabel.bright("Database driver:"), formPanel, 0, row);
            WidgetUtils.addToGridBag(_databaseDriverComboBox, formPanel, 1, row);

            row++;
            WidgetUtils.addToGridBag(DCLabel.bright("Driver class name:"), formPanel, 0, row);
            WidgetUtils.addToGridBag(_driverClassNameTextField, formPanel, 1, row);

            row++;
            WidgetUtils.addToGridBag(_multipleConnectionsCheckBox, formPanel, 1, row);
        }

        final JButton testButton = WidgetFactory.createDefaultButton(getTestButtonText(), IconUtils.ACTION_REFRESH);
        testButton.addActionListener(event -> {
            final JdbcDatastore datastore = createDatastore();

            final List connections = new ArrayList<>();

            try {
                if (datastore.isMultipleConnections()) {
                    final DataSource ds = datastore.createDataSource();
                    for (int i = 0; i < TEST_CONNECTION_COUNT; i++) {
                        final Connection connection = ds.getConnection();
                        connections.add(connection);
                    }
                } else {
                    final Connection connnection = datastore.createConnection();
                    connections.add(connnection);
                }
            } catch (final Throwable e) {
                WidgetUtils.showErrorMessage("Could not establish connection", e);
                return;
            } finally {
                for (final Connection connection : connections) {
                    FileHelper.safeClose(connection);
                }
            }

            JOptionPane.showMessageDialog(JdbcDatastoreDialog.this, "Connection successful!");
        });

        _multipleConnectionsCheckBox.addListener((item, selected) -> testButton.setText(getTestButtonText()));

        final DCPanel buttonPanel = getButtonPanel();
        buttonPanel.add(testButton);

        final DCPanel formContainerPanel = new DCPanel(WidgetUtils.COLOR_ALTERNATIVE_BACKGROUND);
        formContainerPanel.setLayout(new BorderLayout());
        formContainerPanel.setBorder(WidgetUtils.BORDER_TOP_PADDING);
        formContainerPanel.add(formPanel, BorderLayout.NORTH);
        formContainerPanel.add(_tabbedPane, BorderLayout.CENTER);
        formContainerPanel.add(buttonPanel, BorderLayout.SOUTH);

        final DCPanel outerPanel = new DCPanel();
        outerPanel.setLayout(new BorderLayout());

        outerPanel.add(new DescriptionLabel("Use this dialog to connect to your relational database. "
                + "Connections are made using a Connection string (aka. a JDBC URL) and a set of credentials. "
                + "These can be entered directly in the 'Generic connection parameters' panel. "
                + "If you see an additional panel, this provides an alternative means of connecting "
                + "without having to know the URL format for your specific database type."), BorderLayout.NORTH);
        outerPanel.add(formContainerPanel, BorderLayout.CENTER);
        // Uncomment to add the status bar. It is hidden by default as no
        // validation on generic and specific connection settings is in place,
        // so the status bar is displaying "Datastore ready" in situations when
        // it is actually not ready.
        // outerPanel.add(_statusLabel, BorderLayout.SOUTH);

        outerPanel.setPreferredSize(getDialogWidth(), 500);

        return outerPanel;
    }

    private String getTestButtonText() {
        if (_multipleConnectionsCheckBox.isSelected()) {
            return "Test connections";
        }
        return "Test connection";
    }

    @Override
    protected JdbcDatastore createDatastore() {
        final String datastoreName = _datastoreNameTextField.getText();
        if (StringUtils.isNullOrEmpty(datastoreName)) {
            throw new IllegalStateException("Please enter a datastore name");
        }

        final int connectionPresenterIndex = _tabbedPane.getSelectedIndex();
        final DatabaseConnectionPresenter connectionPresenter = _connectionPresenters[connectionPresenterIndex];

        logger.info("Creating datastore using connection presenter ({}): {}", connectionPresenterIndex,
                connectionPresenter);

        final String driverClass = _driverClassNameTextField.getText();
        final String connectionString = connectionPresenter.getJdbcUrl();
        final String username = connectionPresenter.getUsername();
        final String password = connectionPresenter.getPassword();
        final boolean multipleConnections = _multipleConnectionsCheckBox.isSelected();

        return new JdbcDatastore(datastoreName, connectionString, driverClass, username, password, multipleConnections);
    }

    @Override
    public String getWindowTitle() {
        return "Database connection | Datastore";
    }

    @Override
    protected String getDatastoreIconPath() {
        return IconUtils.GENERIC_DATASTORE_IMAGEPATH;
    }
}