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

com.tacitknowledge.util.migration.jdbc.SqlScriptMigrationTaskSource Maven / Gradle / Ivy

/* Copyright 2004 Tacit Knowledge
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.tacitknowledge.util.migration.jdbc;

import com.tacitknowledge.util.discovery.ClassDiscoveryUtil;
import com.tacitknowledge.util.migration.MigrationException;
import com.tacitknowledge.util.migration.MigrationTask;
import com.tacitknowledge.util.migration.MigrationTaskSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Search a package (directory) for SQL scripts that a specific pattern and
 * returns corresponding SqlScriptMigrationTasks. The name of
 * each script must follow the pattern of "patch(\d+)(_.+)?\.sql".
 *
 * @author Scott Askew ([email protected])
 */
public class SqlScriptMigrationTaskSource implements MigrationTaskSource
{
    /**
     * Class logger
     */
    private static Log log = LogFactory
            .getLog(SqlScriptMigrationTaskSource.class);

    /**
     * The regular expression used to match SQL patch files.
     */
    private static final String SQL_PATCH_REGEX = "^patch(\\d++)(?!-rollback)_?(.+)?\\.sql";

    /**
     * The regular expression used to match SQL rollback files
     */
    private static final String SQL_ROLLBACK_REGEX = "^patch(\\d++)-rollback_?(.+)?\\.sql";

    /**
     * {@inheritDoc}
     */
    public List getMigrationTasks(String packageName) throws MigrationException
    {
        String path = packageName.replace('.', '/');

        String[] upScripts = getSqlScripts(path, SQL_PATCH_REGEX);
        String[] downScripts = getSqlScripts(path, SQL_ROLLBACK_REGEX);

        return createMigrationScripts(upScripts, downScripts);
    }

    /**
     * Returns the SQL scripts which exist in the specified patch and match the specified regex.
     *
     * @param path  the Path to search
     * @param regex the regex which the scripts should match
     * @return a String array of script names.
     */
    private String[] getSqlScripts(String path, String regex)
    {
        String[] scripts = ClassDiscoveryUtil.getResources(path, regex);
        if (log.isDebugEnabled())
        {
            log.debug("Found " + scripts.length + " patches in path: " + path);
            for (int i = 0; i < scripts.length; i++)
            {
                log.debug(" -- \"" + scripts[i] + "\"");
            }
        }
        return scripts;
    }

    /**
     * Creates a list of SqlScriptMigrationTasks based on the
     * array of SQL scripts.
     *
     * @param upScripts   the classpath-relative array of SQL migration scripts
     * @param downScripts the classpath-relative array of SQL migration scripts
     * @return a list of SqlScriptMigrationTasks based on the
     *         array of SQL scripts
     * @throws MigrationException if a SqlScriptMigrationTask could no be created
     */
    private List createMigrationScripts(String[] upScripts, String[] downScripts)
            throws MigrationException
    {
        Pattern upFileNamePattern = Pattern.compile(SQL_PATCH_REGEX);
        Pattern downFileNamePattern = Pattern.compile(SQL_ROLLBACK_REGEX);

        List tasks = new ArrayList();
        for (int i = 0; i < upScripts.length; i++)
        {
            String script = upScripts[i];

            if (script != null)
            {
                // get the file name
                File scriptFile = new File(script);
                String scriptFileName = scriptFile.getName();

                // Get the version out of the script name
                int order = getOrder(upFileNamePattern, script, scriptFileName);

                // get the down script which matches this patch order
                String downScript = getMatchingDownScript(downScripts, order,
                        downFileNamePattern);

                // read the scripts
                String upSql = readSql(getInputStream(script));
                String downSql = "";

                // if the down script is not the empty string, then try to read
                // it.
                if (!"".equals(downScript))
                    downSql = readSql(getInputStream(downScript));

                // create a new task
                SqlScriptMigrationTask task = new SqlScriptMigrationTask(
                        scriptFileName, order, upSql, downSql);
                task.setName(scriptFileName);

                // add the task to the list of tasks
                tasks.add(task);
            }
        }
        return tasks;
    }

    /**
     * Returns the filename of the downscript which matches the order of the order parameter.
     *
     * @param downScripts an array of scripts
     * @param order       the order of the down script whose name should be returned
     * @return the name of the down script that matches the order
     */
    private String getMatchingDownScript(String[] downScripts, int order,
            Pattern fileNamePattern) throws MigrationException
    {
        boolean isScriptFound = false;
        String script = "";
        for (int i = 0; i < downScripts.length && !isScriptFound; i++)
        {
            File scriptFile = new File(downScripts[i]);
            String scriptFileName = scriptFile.getName();
            int downScriptOrder = getOrder(fileNamePattern, downScripts[i],
                    scriptFileName);

            if (downScriptOrder == order)
            {
                script = downScripts[i];
                isScriptFound = true;
            }
        }

        if (!isScriptFound)
        {
            log.info("There was no rollback script for patch level: " + order);
        }
        return script;
    }

    /**
     * Returns an input stream that points to the script name
     *
     * @param scriptName the name of the script to create an InputStream
     * @return an InputStream returns an InputStream based upon the scriptName
     */
    private InputStream getInputStream(String scriptName)
    {
        scriptName = scriptName.replace('\\', '/');
        log.debug("Examining possible SQL patch file \"" + scriptName + "\"");
        return Thread.currentThread().getContextClassLoader()
                .getResourceAsStream(scriptName);
    }

    /**
     * Reads the file contents into a String object.
     *
     * @param is the InputStream
     * @return a String with the contents of the InputStream
     * @throws MigrationException if there's an error reading in the contents
     */
    private String readSql(InputStream is) throws MigrationException
    {
        StringBuffer sqlBuffer = new StringBuffer();
        BufferedReader buf = new BufferedReader(new InputStreamReader(is));

        try
        {
            String line = buf.readLine();
            while (line != null)
            {
                sqlBuffer.append(line).append("\n");
                line = buf.readLine();
            }
        }
        catch (IOException ioe)
        {
            throw new MigrationException(
                    "There was an error reading in a script", ioe);

        }
        finally
        {
            try
            {
                is.close();
            }
            catch (IOException ioe)
            {
                log.error("Could not close input stream", ioe);
            }
        }
        return sqlBuffer.toString();
    }

    /**
     * Returns the order for the file.
     *
     * @param p              a Pattern defining the file name pattern
     * @param script         the Script
     * @param scriptFileName the name of the file
     * @return an int indicating the order
     * @throws MigrationException in case the file name is invalid
     */
    private int getOrder(Pattern p, String script, String scriptFileName)
            throws MigrationException
    {
        Matcher matcher = p.matcher(scriptFileName);
        if (!matcher.matches() || matcher.groupCount() != 2)
        {
            throw new MigrationException("Invalid SQL script name: " + script);
        }
        int order = Integer.parseInt(matcher.group(1));
        return order;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy