org.openmrs.util.databasechange.SourceMySqldiffFile.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.util.databasechange.SourceMySqldiffFile.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
 *
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
 * graphic logo is a trademark of OpenMRS Inc.
 */
package org.openmrs.util.databasechange;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

import liquibase.change.custom.CustomTaskChange;
import liquibase.database.Database;
import liquibase.database.DatabaseConnection;
import liquibase.exception.CustomChangeException;
import liquibase.exception.DatabaseException;
import liquibase.exception.SetupException;

import liquibase.exception.ValidationErrors;
import liquibase.resource.ResourceAccessor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.api.context.Context;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.OpenmrsUtil;

/**
 * Executes (aka "source"s) the given file on the current database. <br>
 * <br>
 * Expects parameter: "sqlFile" : name of file on classpath to source on mysql
 */
public class SourceMySqldiffFile implements CustomTaskChange {

    public static final String CONNECTION_USERNAME = "connection.username";

    public static final String CONNECTION_PASSWORD = "connection.password";

    private static Log log = LogFactory.getLog(SourceMySqldiffFile.class);

    /**
     * Absolute path and name of file to source
     */
    private String sqlFile = null;

    private ResourceAccessor fileOpener = null;

    /**
     * Does the work of executing the file on mysql
     *
     * @see liquibase.change.custom.CustomTaskChange#execute(liquibase.database.Database)
     */
    @Override
    public void execute(Database database) throws CustomChangeException {

        Properties runtimeProperties = Context.getRuntimeProperties();

        String username = runtimeProperties.getProperty(CONNECTION_USERNAME);
        String password = runtimeProperties.getProperty(CONNECTION_PASSWORD);

        if (username == null) {
            username = System.getProperty(CONNECTION_USERNAME);
        }
        if (password == null) {
            password = System.getProperty(CONNECTION_PASSWORD);
        }

        // if we're in a "generate sql file" mode, quit early
        if (username == null || password == null) {
            return;
        }

        DatabaseConnection connection = database.getConnection();

        // copy the file from the classpath to a real file
        File tmpOutputFile = null;
        try {
            tmpOutputFile = File.createTempFile(sqlFile, "tmp");
            InputStream sqlFileInputStream = fileOpener.getResourceAsStream(sqlFile);
            OutputStream outputStream = new FileOutputStream(tmpOutputFile);
            OpenmrsUtil.copyFile(sqlFileInputStream, outputStream);
        } catch (IOException e) {
            if (tmpOutputFile != null) {
                throw new CustomChangeException(
                        "Unable to copy " + sqlFile + " to file: " + tmpOutputFile.getAbsolutePath(), e);
            } else {
                throw new CustomChangeException("Unable to copy " + sqlFile, e);
            }
        }

        // build the mysql command line string
        List<String> commands = new ArrayList<String>();
        String databaseName;
        try {
            commands.add("mysql");
            commands.add("-u" + username);
            commands.add("-p" + password);
            String path = tmpOutputFile.getAbsolutePath();
            if (!OpenmrsConstants.UNIX_BASED_OPERATING_SYSTEM) {
                // windows hacks
                path = fixWindowsPathHack(path);
            }

            commands.add("-esource " + path);
            databaseName = connection.getCatalog();
            commands.add(databaseName);
        } catch (DatabaseException e) {
            throw new CustomChangeException("Unable to generate command string for file: " + sqlFile, e);
        }

        // to be used in error messages if this fails
        String errorCommand = "\"mysql -u" + username + " -p -e\"source " + tmpOutputFile.getAbsolutePath() + "\""
                + databaseName;

        // run the command line string
        StringBuffer output = new StringBuffer();
        Integer exitValue = -1; // default to a non-zero exit value in case of exceptions
        try {
            exitValue = execCmd(tmpOutputFile.getParentFile(), commands.toArray(new String[] {}), output);
        } catch (IOException io) {
            if (io.getMessage().endsWith("not found")) {
                throw new CustomChangeException("Unable to run command: " + commands.get(0)
                        + ".  Make sure that it is on the PATH and then restart your server and try again. "
                        + " Or run " + errorCommand + " at the command line with the appropriate full mysql path",
                        io);
            }
        } catch (Exception e) {
            throw new CustomChangeException("Error while executing command: '" + commands.get(0) + "'", e);
        }

        log.debug("Exec called: " + Arrays.asList(commands));

        if (exitValue != 0) {
            log.error("There was an error while running the " + commands.get(0) + " command.  Command output: "
                    + output.toString());
            throw new CustomChangeException("There was an error while running the " + commands.get(0)
                    + " command. See your server's error log for the full error output. As an alternative, you"
                    + " can run this command manually on your database to skip over this error.  Run this at the command line "
                    + errorCommand + "  ");
        } else {
            // a normal exit value
            log.debug("Output of exec: " + output);
        }

    }

    /**
     * A hacky way to get rid of the spaces in the java exec call because mysql and java are not
     * communicating well
     *
     * @param path
     * @return
     */
    private String fixWindowsPathHack(String path) {
        StringBuilder returnedPath = new StringBuilder();
        path = path.replace("\\", "/"); // so java doesn't freak out with windows backslashes
        for (String pathPart : path.split("/")) {
            if (pathPart.contains(" ")) {
                // shorten to the first 6 characters uppercased
                pathPart = pathPart.substring(0, 6).toUpperCase();
                // add in the tilda and assume the first one (very hacky part)
                pathPart = pathPart + "~1";
            }
            returnedPath.append(pathPart).append("/");
        }
        returnedPath.deleteCharAt(returnedPath.length() - 1);
        return returnedPath.toString();
    }

    /**
     * @param cmdWithArguments
     * @param wd
     * @param the string
     * @return process exit value
     */
    private Integer execCmd(File wd, String[] cmdWithArguments, StringBuffer out) throws Exception {
        log.debug("executing command: " + Arrays.toString(cmdWithArguments));

        Integer exitValue = -1;

        // Needed to add support for working directory because of a linux
        // file system permission issue.

        if (!OpenmrsConstants.UNIX_BASED_OPERATING_SYSTEM) {
            wd = null;
        }

        Process p = (wd != null) ? Runtime.getRuntime().exec(cmdWithArguments, null, wd)
                : Runtime.getRuntime().exec(cmdWithArguments);

        out.append("Normal cmd output:\n");
        Reader reader = new InputStreamReader(p.getInputStream());
        BufferedReader input = new BufferedReader(reader);
        int readChar = 0;
        while ((readChar = input.read()) != -1) {
            out.append((char) readChar);
        }
        input.close();
        reader.close();

        out.append("ErrorStream cmd output:\n");
        reader = new InputStreamReader(p.getErrorStream());
        input = new BufferedReader(reader);
        readChar = 0;
        while ((readChar = input.read()) != -1) {
            out.append((char) readChar);
        }
        input.close();
        reader.close();

        exitValue = p.waitFor();

        log.debug("Process exit value: " + exitValue);

        log.debug("execCmd output: \n" + out.toString());

        return exitValue;
    }

    /**
     * @see liquibase.change.custom.CustomChange#getConfirmationMessage()
     */
    @Override
    public String getConfirmationMessage() {
        return "Finished executing " + sqlFile + " on database";
    }

    /**
     * @see liquibase.change.custom.CustomChange#setFileOpener(ResourceAccessor) 
     */
    @Override
    public void setFileOpener(ResourceAccessor fileOpener) {
        this.fileOpener = fileOpener;
    }

    /**
     * Get the values of the parameters passed in and set them to the local variables on this class.
     *
     * @see liquibase.change.custom.CustomChange#setUp()
     */
    @Override
    public void setUp() throws SetupException {

    }

    /**
     * @see liquibase.change.custom.CustomChange#validate(liquibase.database.Database)
     */
    @Override
    public ValidationErrors validate(Database database) {
        return new ValidationErrors();
    }

    /**
     * @param sqlFile the sqlFile to set
     */
    public void setSqlFile(String sqlFile) {
        this.sqlFile = sqlFile;
    }

}