org.intermine.install.swing.CreateDatabaseWorker.java Source code

Java tutorial

Introduction

Here is the source code for org.intermine.install.swing.CreateDatabaseWorker.java

Source

package org.intermine.install.swing;

/*
 * Copyright (C) 2002-2013 FlyMine
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  See the LICENSE file for more
 * information or http://www.gnu.org/copyleft/lesser.html.
 *
 */

import static org.intermine.install.properties.InterminePropertyKeys.ITEMS_ENCODING;
import static org.intermine.install.properties.InterminePropertyKeys.ITEMS_NAME;
import static org.intermine.install.properties.InterminePropertyKeys.ITEMS_PASSWORD;
import static org.intermine.install.properties.InterminePropertyKeys.ITEMS_SERVER;
import static org.intermine.install.properties.InterminePropertyKeys.ITEMS_USER_NAME;
import static org.intermine.install.properties.InterminePropertyKeys.PRODUCTION_ENCODING;
import static org.intermine.install.properties.InterminePropertyKeys.PRODUCTION_NAME;
import static org.intermine.install.properties.InterminePropertyKeys.PRODUCTION_PASSWORD;
import static org.intermine.install.properties.InterminePropertyKeys.PRODUCTION_SERVER;
import static org.intermine.install.properties.InterminePropertyKeys.PRODUCTION_USER_NAME;
import static org.intermine.install.properties.InterminePropertyKeys.PROFILE_ENCODING;
import static org.intermine.install.properties.InterminePropertyKeys.PROFILE_NAME;
import static org.intermine.install.properties.InterminePropertyKeys.PROFILE_PASSWORD;
import static org.intermine.install.properties.InterminePropertyKeys.PROFILE_SERVER;
import static org.intermine.install.properties.InterminePropertyKeys.PROFILE_USER_NAME;

import java.awt.Window;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import javax.swing.SwingWorker;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.intermine.common.swing.Messages;
import org.intermine.common.swing.ProgressDialog;
import org.intermine.install.database.DatabaseConnectionException;
import org.intermine.install.database.DatabaseCreationException;
import org.intermine.install.database.DatabaseUtil;
import org.intermine.install.database.PasswordException;

/**
 * Swing worker for creating the Intermine Postgres databases.
 */
class CreateDatabaseWorker extends SwingWorker<Boolean, String> {
    /**
     * Name for the completed property change.
     */
    static final String COMPLETE = "completed";

    /**
     * Logger.
     */
    private Log logger = LogFactory.getLog(getClass());

    /**
     * The parent window that launched this worker.
     */
    private Window launcher;

    /**
     * The mine creation properties.
     */
    private Properties properties;

    /**
     * A progress monitor dialog for watching this worker's progress.
     */
    private ProgressDialog monitor;

    /**
     * The error raised if the database creation fails.
     */
    private Exception creationException;

    /**
     * Set of strings identifying the database that have been created or
     * checked.
     */
    private Set<String> doneDatabases = Collections.synchronizedSet(new HashSet<String>());

    /**
     * Initialise with a launching parent window and mine creation properties.
     * 
     * @param parent The parent Window.
     * @param props The mine properties.
     */
    CreateDatabaseWorker(Window parent, Properties props) {
        launcher = parent;
        properties = props;
        monitor = new ProgressDialog(parent, true, true);
        monitor.setTitle(Messages.getMessage("database.create.title"));
        monitor.setTaskProgressIndeterminate(true);
        monitor.setTask(this, true);
        monitor.setSize(400, monitor.getHeight());
        monitor.resetOverallProgress(0, 3);
    }

    /**
     * Get the exception raised if database creation or connection fails.
     * 
     * @return The exception from the failure, or <code>null</code> if there
     * was no failure.
     */
    public Exception getCreationException() {
        return creationException;
    }

    /**
     * Called in the Swing event thread to update the progress dialog according
     * to the databases checked or created.
     * 
     * @param chunks The databases completed since the last call to this method.
     * 
     * @see #publish
     */
    @Override
    protected void process(List<String> chunks) {

        doneDatabases.addAll(chunks);

        int progress = Math.min(doneDatabases.size(), 3);

        monitor.setInformationText(Messages.getMessage("database.create.progress." + progress));
        monitor.setOverallProgress(progress);

        if (progress <= 2) {
            if (!monitor.isVisible()) {
                monitor.positionOver(launcher);
                monitor.setVisible(true);
                monitor.toFront();
            }
        }
    }

    /**
     * Execute the creation or checks of the databases. Calls <code>process</code>
     * as each database is completed.
     * 
     * @return <code>true</code> if all goes well, <code>false</code> if there is
     * a failure.
     * 
     * @see #process
     */
    @Override
    protected Boolean doInBackground() {

        doneDatabases.clear();

        publish();
        try {
            if (isCancelled()) {
                return Boolean.FALSE;
            }

            String server = properties.getProperty(PRODUCTION_SERVER);
            String dbName = properties.getProperty(PRODUCTION_NAME);
            String userName = properties.getProperty(PRODUCTION_USER_NAME);
            String password = properties.getProperty(PRODUCTION_PASSWORD);
            String encoding = properties.getProperty(PRODUCTION_ENCODING);
            if (!DatabaseUtil.checkDatabaseExists(server, dbName, userName, password)) {
                logger.info("Creating production database " + dbName + " on " + server);
                createDatabase(server, dbName, userName, password, encoding);
            }
            if (isCancelled()) {
                return Boolean.FALSE;
            }
            publish("production");

            server = properties.getProperty(ITEMS_SERVER);
            dbName = properties.getProperty(ITEMS_NAME);
            userName = properties.getProperty(ITEMS_USER_NAME);
            password = properties.getProperty(ITEMS_PASSWORD);
            encoding = properties.getProperty(ITEMS_ENCODING);
            if (!DatabaseUtil.checkDatabaseExists(server, dbName, userName, password)) {
                logger.info("Creating common target items database " + dbName + " on " + server);
                createDatabase(server, dbName, userName, password, encoding);
            }
            if (isCancelled()) {
                return Boolean.FALSE;
            }
            publish("items");

            server = properties.getProperty(PROFILE_SERVER);
            dbName = properties.getProperty(PROFILE_NAME);
            userName = properties.getProperty(PROFILE_USER_NAME);
            password = properties.getProperty(PROFILE_PASSWORD);
            encoding = properties.getProperty(PROFILE_ENCODING);
            if (!DatabaseUtil.checkDatabaseExists(server, dbName, userName, password)) {
                logger.info("Creating user profiles database " + dbName + " on " + server);
                createDatabase(server, dbName, userName, password, encoding);
            }
            if (!isCancelled()) {
                publish("profiles");
            }

            return Boolean.TRUE;
        } catch (InterruptedException e) {
            // Nothing, just end.
        } catch (IOException e) {
            creationException = e;
        } catch (DatabaseConnectionException e) {
            creationException = e;
        }
        return Boolean.FALSE;
    }

    /**
     * Called in the Swing event when the worker is finished. Fires the
     * "complete" property change event.
     */
    @Override
    protected void done() {
        monitor.setVisible(false);
        firePropertyChange(COMPLETE, false, true);
    }

    /**
     * Create a database.
     * 
     * @param server The database server.
     * @param databaseName The database name.
     * @param userName The database user name.
     * @param password The user password.
     * @param encoding The encoding for the database.
     * 
     * @throws IOException if there is a problem when executed as an external
     * <code>createdb</code> process ({@link #createDatabaseWithCreatedb}).
     *  
     * @throws InterruptedException if the thread is interrupted while running
     * as an external process ({@link #createDatabaseWithCreatedb}).
     * 
     * @throws DatabaseConnectionException if there is an issue with creating
     * or checking the database.
     */
    protected void createDatabase(String server, String databaseName, String userName, String password,
            String encoding) throws IOException, InterruptedException, DatabaseConnectionException {
        createDatabaseWithSQL(server, databaseName, userName, password, encoding);
    }

    /**
     * Create a database via SQL <code>CREATE DATABASE</code> statements through
     * an JDBC connection. 
     * 
     * @param server The database server.
     * @param databaseName The database name.
     * @param userName The database user name.
     * @param password The user password.
     * @param encoding The encoding for the database.
     * 
     * @throws DatabaseCreateException if there is a problem when creating the database.
     * @throws PasswordException if the user name and password are rejected by Postgres.
     * @throws DatabaseConnectionException if there are other problems connecting
     * to Postgres.
     */
    protected void createDatabaseWithSQL(String server, String databaseName, String userName, String password,
            String encoding) throws DatabaseConnectionException {

        String url = DatabaseUtil.formJdbcUrl(server, "postgres");

        try {
            Connection conn = DriverManager.getConnection(url, userName, password);
            try {
                StringBuilder sql = new StringBuilder();
                sql.append("create database \"").append(databaseName).append("\" with owner ");
                sql.append(userName).append(" template template0 encoding '");
                sql.append(encoding).append("'");

                logger.debug(sql);
                conn.createStatement().execute(sql.toString());
            } finally {
                conn.close();
            }
        } catch (SQLException e) {

            String message = e.getMessage();
            if (DatabaseUtil.ALREADY_EXISTS_PATTERN.matcher(message).matches()) {
                logger.info("Database '" + databaseName + "' already exists.");
            } else if (DatabaseUtil.PASSWORD_PATTERN.matcher(message).matches()) {
                throw new PasswordException(databaseName,
                        "Password authentication failed for user '" + userName + "'.");
            } else {
                throw new DatabaseCreationException(e);
            }
        }
    }

    /**
     * Create a database using the Postgres <code>createdb</code> program.
     * 
     * @param server The database server.
     * @param databaseName The database name.
     * @param userName The database user name.
     * @param password The user password.
     * @param encoding The encoding for the database.
     * 
     * @throws IOException if there is a problem running <code>createdb</code>.
     *  
     * @throws InterruptedException if the thread is interrupted while running
     * the <code>createdb</code>.
     * 
     * @throws DatabaseCreationException if the process completes but failed
     * to create the database.
     */
    protected void createDatabaseWithCreatedb(String server, String databaseName, String userName, String password,
            String encoding) throws IOException, InterruptedException, DatabaseCreationException {

        String[] commands = { "/usr/bin/createdb", "-h", server, "-E", encoding, "-O", userName, "-W", "-T",
                "template0", databaseName };

        if (logger.isDebugEnabled()) {
            StringBuilder command = new StringBuilder();
            for (int i = 0; i < commands.length; i++) {
                if (i > 0) {
                    command.append(' ');
                }
                command.append(commands[i]);
            }
            logger.debug(command);
        }

        StringBuilder output = new StringBuilder();
        StringBuilder errorOutput = new StringBuilder();

        Integer exitCode = null;
        Process p = Runtime.getRuntime().exec(commands);
        try {
            BufferedReader stdin = new BufferedReader(new InputStreamReader(p.getInputStream()));
            BufferedReader stderr = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            boolean passwordSet = false;
            while (true) {
                try {
                    while (stdin.ready()) {
                        char c = (char) stdin.read();
                        output.append(c);
                    }
                    while (stderr.ready()) {
                        char c = (char) stderr.read();
                        errorOutput.append(c);
                    }

                    if (!passwordSet && errorOutput.indexOf("Password:") >= 0) {
                        PrintStream out = new PrintStream(p.getOutputStream(), true);
                        out.println(password);
                        passwordSet = true;
                    }

                    exitCode = p.exitValue();
                    // If this succeeds, we're done.
                    break;
                } catch (IllegalThreadStateException e) {
                    // Process not done, so wait and continue.
                    Thread.sleep(250);
                }
            }
        } finally {
            try {
                p.exitValue();
            } catch (IllegalThreadStateException e) {
                // Not finished, but something has failed.
                p.destroy();
            }
        }

        if (errorOutput.length() > 0) {
            throw new DatabaseCreationException(exitCode, output.toString(), errorOutput.toString());
        }

        if (exitCode != 0) {
            throw new DatabaseCreationException("Return code from createdb = " + exitCode, exitCode,
                    output.toString(), errorOutput.toString());
        }
    }
}