/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2010-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2010-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This 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
* Lesser General Public License for more details.
*/
package org.geotoolkit.internal.wizard;
import java.io.File;
import java.io.IOException;
import java.util.Set;
import java.util.Map;
import java.util.Locale;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.BorderLayout;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.text.Document;
import javax.swing.text.NumberFormatter;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.jdesktop.swingx.JXLabel;
import org.jdesktop.swingx.JXLoginPane;
import org.jdesktop.swingx.auth.JDBCLoginService;
import org.netbeans.spi.wizard.WizardController;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.resources.Widgets;
import org.geotoolkit.resources.Wizards;
import org.geotoolkit.resources.Vocabulary;
import org.geotoolkit.resources.Descriptions;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.internal.io.Host;
import org.geotoolkit.internal.io.Installation;
import org.geotoolkit.internal.sql.PostgisInstaller;
import org.geotoolkit.internal.sql.CoverageDatabaseInstaller;
import org.geotoolkit.internal.sql.Dialect;
import org.geotoolkit.internal.swing.DocumentChangeListener;
/**
* Guides the user through the steps of creating a coverage database.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.11
*
* @since 3.11
* @module
*/
public final class CoverageDatabaseWizard extends AbstractWizard {
/**
* The preference key for the PostGIS directory.
*/
private static final String POSTGIS_PREFS = "PostGIS directory";
/**
* The default size for content panes.
*/
private static final Dimension SIZE = new Dimension(600, 400);
/**
* The ID of the chooser for entering all required informations.
*/
static final String CONNECTION="CONNECTION", POSTGIS="POSTGIS", CONFIGURE="CONFIGURE", CONFIRM="CONFIRM";
/**
* The field for selecting the database engine.
*/
private JComboBox<String> engine;
/**
* Fields to be provided by users.
*/
JTextField server, database, schema, admin, user;
/**
* Fields to be provided by users.
*/
private JFormattedTextField port;
/**
* The directory which contains PostGIS files.
*/
JFileChooser postgis;
/**
* Choices to be provided by users.
*/
JCheckBox createRoles, createEPSG, setAsDefault, setAsDefaultEPSG;
/**
* The schemas to create. They are listed in the summary panel before to start
* the creation of the database.
*/
String[] schemas;
/**
* Creates a new wizard.
*/
public CoverageDatabaseWizard() {
super(Wizards.format(Wizards.Keys.CoverageDatabaseTitle), new String[] {
CONNECTION,
POSTGIS,
CONFIGURE,
CONFIRM
}, new String[] {
Vocabulary.format(Vocabulary.Keys.ConnectionParameters),
Vocabulary.format(Vocabulary.Keys.SpatialObjects),
Vocabulary.format(Vocabulary.Keys.Configure),
Vocabulary.format(Vocabulary.Keys.Confirm)
});
}
/**
* Creates a panel that represents a named step in the wizard.
*
* @param controller The object which controls whether the Next/Finish buttons in the wizard are enabled.
* @param id The name of the step, one of the array of steps passed in the constructor.
* @param settings A Map containing settings from earlier steps in the wizard.
* @return The component that should be displayed in the center of the wizard.
*/
@Override
@SuppressWarnings("rawtypes")
protected JComponent createPanel(final WizardController controller, final String id, final Map settings) {
final Locale locale = Locale.getDefault(Locale.Category.DISPLAY);
final Vocabulary resources = Vocabulary.getResources(locale);
final Wizards wizardRes = Wizards.getResources(locale);
final JComponent panel;
switch (id) {
// -------------------------------------------------------------------
// Panel 1: Connection parameters
// -------------------------------------------------------------------
case CONNECTION: {
panel = new JPanel(new GridBagLayout());
final GridBagConstraints c = new GridBagConstraints();
c.gridy = 0;
c.anchor = GridBagConstraints.WEST;
engine = new JComboBox<>(new String[] {"PostgreSQL"});
server = new JTextField();
port = new JFormattedTextField(new NumberFormatter());
database = new JTextField();
schema = new JTextField(CoverageDatabaseInstaller.SCHEMA);
admin = new JTextField(CoverageDatabaseInstaller.ADMINISTRATOR);
user = new JTextField(CoverageDatabaseInstaller.USER);
add(panel, resources, Vocabulary.Keys.DatabaseEngine, engine, c);
add(panel, resources, Vocabulary.Keys.Server, server, c);
add(panel, resources, Vocabulary.Keys.Port, port, c);
add(panel, resources, Vocabulary.Keys.Database, database, c);
add(panel, resources, Vocabulary.Keys.Schema, schema, c);
add(panel, resources, Vocabulary.Keys.Administrator, admin , c);
add(panel, resources, Vocabulary.Keys.User, user, c);
final String problem = wizardRes.getString(Wizards.Keys.DatabaseRequired);
final DocumentChangeListener listener = new DocumentChangeListener() {
/** The set of documents having a non-blank text value. */
private final Set<Document> hasText = Collections.newSetFromMap(new IdentityHashMap<Document,Boolean>());
@Override protected void textChanged(final Document document, final String text) {
if (!text.trim().isEmpty() ? hasText.add(document) : hasText.remove(document)) {
final int size = 2 - hasText.size(); // 2 is the amount of fields having this listener.
assert size >= 0 : size;
controller.setProblem(size == 0 ? null : problem);
}
}
};
server .getDocument().addDocumentListener(listener);
database.getDocument().addDocumentListener(listener);
controller.setProblem(problem);
try {
// Initial values, which must be set after the listeners.
final Host host = new Host(Installation.COVERAGES.getDataSource(), null);
if (host.host != null) server.setText(host.host);
if (host.port != null) port.setValue(host.port);
} catch (IOException e) {
Logging.recoverableException(Logging.getLogger("org.geotoolkit.coverage.sql"),
CoverageDatabaseWizard.class, "createPanel", e);
}
addSetting(settings, CONNECTION, panel);
break;
}
// -------------------------------------------------------------------
// Panel 2: Spatial objects
// -------------------------------------------------------------------
case POSTGIS: {
final JXLabel desc = new JXLabel(wizardRes.getString(Wizards.Keys.PostgisDirectory));
desc.setLineWrap(true);
panel = new JPanel(new BorderLayout());
panel.add(desc, BorderLayout.BEFORE_FIRST_LINE);
postgis = new JFileChooser();
postgis.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
postgis.setControlButtonsAreShown(false);
postgis.setMultiSelectionEnabled(false);
postgis.setAcceptAllFileFilterUsed(false);
postgis.addChoosableFileFilter(new FileNameExtensionFilter(resources.getString(Vocabulary.Keys.Files_1, "SQL"), "sql"));
postgis.addPropertyChangeListener(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY, new PropertyChangeListener() {
@Override public void propertyChange(final PropertyChangeEvent event) {
String missing = PostgisInstaller.INSTALL;
final File directory = (File) event.getNewValue();
if (directory != null) {
if (new File(directory, missing).isFile() ||
new File(directory, PostgisInstaller.LEGACY).isFile())
{
missing = PostgisInstaller.REF_SYS;
if (new File(directory, missing).isFile()) {
missing = null;
}
}
}
if (missing != null) {
missing = Errors.format(Errors.Keys.FileDoesNotExist_1, missing);
}
controller.setProblem(missing);
}
});
controller.setProblem(Widgets.format(Widgets.Keys.SelectDirectory));
final File directory = new File(preferences().get(POSTGIS_PREFS, System.getProperty("user.dir", ".")));
postgis.setCurrentDirectory(directory.getParentFile());
panel.add(postgis, BorderLayout.CENTER);
addSetting(settings, POSTGIS, panel);
break;
}
// -------------------------------------------------------------------
// Panel 3: Configure whatever we want roles, EPSG database, etc.
// -------------------------------------------------------------------
case CONFIGURE: {
createRoles = new JCheckBox(wizardRes.getString(Wizards.Keys.CreateRoles_2, admin.getText(), user.getText()), true);
createEPSG = new JCheckBox(wizardRes.getString(Wizards.Keys.CreateEpsg), true);
setAsDefaultEPSG = new JCheckBox(wizardRes.getString(Wizards.Keys.SetAsDefault_1, "EPSG"));
setAsDefault = new JCheckBox(wizardRes.getString(Wizards.Keys.SetAsDefault_1, "Coverages"));
createEPSG.addActionListener(new ActionListener() {
@Override public void actionPerformed(final ActionEvent event) {
setAsDefaultEPSG.setEnabled(createEPSG.isSelected());
}
});
// The warning will be made visible or hidden using the 'setForeground' method.
// We do not use the 'setVisible(boolean)' method because we want to keep the
// layout unchanged.
final JLabel passwordWarning = new JLabel(Descriptions.getResources(locale)
.getString(Descriptions.Keys.PasswordNotEncrypted));
passwordWarning.setForeground(passwordWarning.getBackground());
final ActionListener listener = new ActionListener() {
@Override public void actionPerformed(final ActionEvent event) {
final boolean enabled = setAsDefault.isSelected() ||
(createEPSG.isSelected() && setAsDefaultEPSG.isSelected());
passwordWarning.setForeground(enabled ? Color.RED : passwordWarning.getBackground());
}
};
createEPSG .addActionListener(listener);
setAsDefault .addActionListener(listener);
setAsDefaultEPSG.addActionListener(listener);
final JPanel choices = new JPanel(new GridBagLayout());
final GridBagConstraints c = new GridBagConstraints();
c.anchor=GridBagConstraints.WEST;
c.gridy=0;
c.gridx=0;
choices.add(createRoles, c);
c.gridy++;
choices.add(createEPSG, c);
c.gridy++;
c.insets.left=30;
choices.add(setAsDefaultEPSG, c);
c.gridy++;
c.insets.left= 0;
choices.add(setAsDefault, c);
c.gridy++;
c.insets.top =15;
choices.add(passwordWarning, c);
panel = new JPanel(new BorderLayout());
panel.add(choices, BorderLayout.CENTER);
panel.add(new JLabel(wizardRes.getString(Wizards.Keys.CoverageDatabaseNotes_1,
server.getText())), BorderLayout.PAGE_END);
break;
}
// -------------------------------------------------------------------
// Panel 4: Confirm
// -------------------------------------------------------------------
case CONFIRM: {
String schema = this.schema.getText();
if (schema == null || ((schema = schema.trim()).length()) == 0) {
schema = CoverageDatabaseInstaller.SCHEMA;
}
schemas = new String[] {
PostgisInstaller.DEFAULT_SCHEMA,
"EPSG",
CoverageDatabaseInstaller.METADATA_SCHEMA,
schema
};
// NOTE: CoverageDatabaseCreator expect the label to be added directly
// to the pane. Shall not be a pane included in an other pane.
panel = new JPanel(new GridBagLayout());
final GridBagConstraints c = new GridBagConstraints();
c.gridx=0;
c.gridy=0;
c.anchor=GridBagConstraints.LINE_START;
c.insets.top = c.insets.bottom = 6;
for (int i=0; i<schemas.length; i++) {
panel.add(new JLabel(wizardRes.getString(Wizards.Keys.CreatingSchema_1, schemas[i])), c);
c.gridy++;
}
addSetting(settings, CONFIRM, panel);
break;
}
default: {
throw new IllegalArgumentException(id); // Should never happen.
}
}
panel.setPreferredSize(SIZE);
panel.setBorder(BorderFactory.createEmptyBorder(6, 15, 9, 15));
return panel;
}
/**
* Adds a field and its label to the given panel.
*/
private static void add(JComponent panel, JLabel label, JComponent field, GridBagConstraints c) {
label.setLabelFor(field);
c.gridx=0; c.weightx=0; c.fill=GridBagConstraints.NONE; panel.add(label, c);
c.gridx=1; c.weightx=1; c.fill=GridBagConstraints.BOTH; panel.add(field, c);
c.gridy++;
}
/**
* Adds a field and its label to the given panel.
*/
private static void add(JComponent panel, Vocabulary resources, short key, JComponent field, GridBagConstraints c) {
add(panel, new JLabel(resources.getLabel(key)), field, c);
}
/**
* Invoked when the user finished to go through wizard steps.
*
* @param settings The settings provided by the user.
* @return The object which will create the database.
*/
@Override
@SuppressWarnings("rawtypes")
protected Object finish(final Map settings) {
/*
* Fetch now the information that we want to save in user preferences.
* This way, if we fail later in this method, the user setting will be remembered.
*/
preferences().put(POSTGIS_PREFS, postgis.getSelectedFile().toString());
/*
* Construct the JDBC URL, with the optional port number.
*/
final StringBuilder buffer = new StringBuilder("jdbc:postgresql://").append(server.getText());
final Number port = (Number) this.port.getValue();
if (port != null) {
buffer.append(':').append(port);
}
final String url = buffer.append('/').append(database.getText()).toString();
/*
* Show the logging panel now.
*/
final JXLoginPane login = new JXLoginPane(new JDBCLoginService(Dialect.POSTGRESQL.driverClass, url));
login.setUserName(admin.getText());
login.setPreferredSize(new Dimension(400, 300));
switch (JXLoginPane.showLoginDialog((JComponent) settings.get(CONFIRM), login)) {
case SUCCEEDED: {
return new CoverageDatabaseCreator(this, login);
}
}
return null; // User cancelled the operation.
}
}