com.migratebird.script.repository.impl.ArchiveScriptLocation.java Source code

Java tutorial

Introduction

Here is the source code for com.migratebird.script.repository.impl.ArchiveScriptLocation.java

Source

/**
 * Copyright 2014 www.migratebird.com
 *
 * 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.migratebird.script.repository.impl;

import static org.apache.commons.io.IOUtils.closeQuietly;
import static com.migratebird.config.MigratebirdProperties.PROPERTY_BASELINE_REVISION;
import static com.migratebird.config.MigratebirdProperties.PROPERTY_IGNORE_CARRIAGE_RETURN_WHEN_CALCULATING_CHECK_SUM;
import static com.migratebird.config.MigratebirdProperties.PROPERTY_POSTPROCESSINGSCRIPT_DIRNAME;
import static com.migratebird.config.MigratebirdProperties.PROPERTY_QUALIFIERS;
import static com.migratebird.config.MigratebirdProperties.PROPERTY_SCRIPT_ENCODING;
import static com.migratebird.config.MigratebirdProperties.PROPERTY_SCRIPT_FILE_EXTENSIONS;
import static com.migratebird.config.MigratebirdProperties.PROPERTY_SCRIPT_INDEX_REGEXP;
import static com.migratebird.config.MigratebirdProperties.PROPERTY_SCRIPT_PATCH_QUALIFIERS;
import static com.migratebird.config.MigratebirdProperties.PROPERTY_SCRIPT_QUALIFIER_REGEXP;
import static com.migratebird.config.MigratebirdProperties.PROPERTY_SCRIPT_TARGETDATABASE_REGEXP;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;

import org.apache.commons.lang.StringUtils;
import com.migratebird.script.Script;
import com.migratebird.script.ScriptContentHandle;
import com.migratebird.script.executedscriptinfo.ScriptIndexes;
import com.migratebird.script.qualifier.Qualifier;
import com.migratebird.script.repository.ScriptLocation;
import com.migratebird.util.MigrateBirdException;
import com.migratebird.util.ReaderInputStream;
import com.migratebird.util.WriterOutputStream;

/**
 * Script container that reads all scripts from a jar file
 *
*/
public class ArchiveScriptLocation extends ScriptLocation {

    /**
     * Creates a new instance of the {@link ArchiveScriptLocation}, while there is no jar file available yet.
     * This constructor can be used to initialize the container while the scripts are still on the file system,
     * and to write the jar file afterwards.
     *
     * @param scripts                     The scripts contained in the container, not null
     * @param scriptEncoding              Encoding used to read the contents of the script, not null
     * @param postProcessingScriptDirName The directory name that contains post processing scripts, may be null
     * @param registeredQualifiers        the registered qualifiers, not null
     * @param patchQualifiers             The qualifiers that indicate that this script is a patch script, not null
     * @param scriptIndexRegexp           The regexp that identifies the script index in the filename, not null
     * @param qualifierRegexp             The regexp that identifies a qualifier in the filename, not null
     * @param targetDatabaseRegexp        The regexp that indicates the target database in the filename, not null
     * @param scriptFileExtensions        The script file extensions
     * @param baseLineRevision            The baseline revision. If set, all scripts with a lower revision will be ignored
     * @param ignoreCarriageReturnsWhenCalculatingCheckSum
     *                                    If true, carriage return chars will be ignored when calculating check sums
     */
    public ArchiveScriptLocation(SortedSet<Script> scripts, String scriptEncoding,
            String postProcessingScriptDirName, Set<Qualifier> registeredQualifiers, Set<Qualifier> patchQualifiers,
            String scriptIndexRegexp, String qualifierRegexp, String targetDatabaseRegexp,
            Set<String> scriptFileExtensions, ScriptIndexes baseLineRevision,
            boolean ignoreCarriageReturnsWhenCalculatingCheckSum) {
        super(scripts, scriptEncoding, postProcessingScriptDirName, registeredQualifiers, patchQualifiers,
                scriptIndexRegexp, qualifierRegexp, targetDatabaseRegexp, scriptFileExtensions, baseLineRevision,
                ignoreCarriageReturnsWhenCalculatingCheckSum);
    }

    /**
     * Creates a new instance based on the contents of the given jar file
     *
     * @param jarLocation                 the jar file
     * @param defaultScriptEncoding       the default script encoding
     * @param defaultPostProcessingScriptDirName
     *                                    the default postprocessing dir name
     * @param defaultRegisteredQualifiers the default registered (allowed) qualifiers
     * @param defaultPatchQualifiers      the default patch qualifiers
     * @param defaultScriptIndexRegexp    the default script index regexp
     * @param defaultQualifierRegexp      the default qualifier regexp
     * @param defaultTargetDatabaseRegexp the default target database regexp
     * @param defaultScriptFileExtensions the default script file extensions
     * @param baseLineRevision            The baseline revision. If set, all scripts with a lower revision will be ignored
     * @param ignoreCarriageReturnsWhenCalculatingCheckSum
     *                                    If true, carriage return chars will be ignored when calculating check sums
     */
    public ArchiveScriptLocation(File jarLocation, String defaultScriptEncoding,
            String defaultPostProcessingScriptDirName, Set<Qualifier> defaultRegisteredQualifiers,
            Set<Qualifier> defaultPatchQualifiers, String defaultScriptIndexRegexp, String defaultQualifierRegexp,
            String defaultTargetDatabaseRegexp, Set<String> defaultScriptFileExtensions,
            ScriptIndexes baseLineRevision, boolean ignoreCarriageReturnsWhenCalculatingCheckSum) {
        super(jarLocation, defaultScriptEncoding, defaultPostProcessingScriptDirName, defaultRegisteredQualifiers,
                defaultPatchQualifiers, defaultScriptIndexRegexp, defaultQualifierRegexp,
                defaultTargetDatabaseRegexp, defaultScriptFileExtensions, baseLineRevision,
                ignoreCarriageReturnsWhenCalculatingCheckSum);
    }

    /**
     * Asserts that the script archive exists
     *
     * @param jarFile The location to validate, not null
     */
    protected void assertValidScriptLocation(File jarFile) {
        File jarFileWithoutSubPath = getJarFileWithoutSubPath(jarFile);
        if (jarFileWithoutSubPath == null || !jarFileWithoutSubPath.exists()) {
            throw new MigrateBirdException("Script jar " + jarFileWithoutSubPath + " does not exist.");
        }
    }

    /**
     * Initializes the scripts from the given jar file
     *
     * @return The scripts, as loaded from the jar
     */
    protected SortedSet<Script> loadScripts(File scriptLocation) {
        String subPath = getJarSubPath(scriptLocation);

        JarFile jarFile = createJarFile(scriptLocation);
        return loadScriptsFromJar(jarFile, subPath);
    }

    protected SortedSet<Script> loadScriptsFromJar(final JarFile jarFile, String subPath) {
        SortedSet<Script> scripts = new TreeSet<Script>();
        for (Enumeration<JarEntry> jarEntries = jarFile.entries(); jarEntries.hasMoreElements();) {
            final JarEntry jarEntry = jarEntries.nextElement();
            String fileName = jarEntry.getName();
            if (LOCATION_PROPERTIES_FILENAME.equals(fileName) || !isScriptFileName(fileName)) {
                continue;
            }

            String relativeScriptName = jarEntry.getName();
            if (subPath != null) {
                if (!fileName.startsWith(subPath)) {
                    continue;
                }
                relativeScriptName = relativeScriptName.substring(subPath.length());
            }
            ScriptContentHandle scriptContentHandle = new ScriptContentHandle(scriptEncoding,
                    ignoreCarriageReturnsWhenCalculatingCheckSum) {
                @Override
                protected InputStream getScriptInputStream() {
                    try {
                        return jarFile.getInputStream(jarEntry);
                    } catch (IOException e) {
                        throw new MigrateBirdException("Error while reading jar entry " + jarEntry, e);
                    }
                }
            };
            Long fileLastModifiedAt = jarEntry.getTime();
            Script script = scriptFactory.createScriptWithContent(relativeScriptName, fileLastModifiedAt,
                    scriptContentHandle);
            scripts.add(script);
        }
        return scripts;
    }

    protected String toQualifiersPropertyValue(Set<Qualifier> qualifiers) {
        StringBuilder propertyValue = new StringBuilder();
        String separator = "";
        for (Qualifier qualifier : qualifiers) {
            propertyValue.append(separator).append(qualifier.getQualifierName());
            separator = ",";
        }
        return propertyValue.toString();
    }

    /**
     * @param scriptLocation The location of the jar file, not null
     * @return The properties as a properties map
     */
    @Override
    protected Properties getCustomProperties(File scriptLocation) {
        InputStream configurationInputStream = null;
        try {
            JarFile jarFile = createJarFile(scriptLocation);
            ZipEntry configurationEntry = jarFile.getEntry(LOCATION_PROPERTIES_FILENAME);
            if (configurationEntry == null) {
                // no custom config found in meta-inf folder, skipping
                return null;
            }
            Properties configuration = new Properties();
            configurationInputStream = jarFile.getInputStream(configurationEntry);
            configuration.load(configurationInputStream);
            return configuration;
        } catch (IOException e) {
            throw new MigrateBirdException("Error while reading configuration file " + LOCATION_PROPERTIES_FILENAME
                    + " from jar file " + scriptLocation, e);
        } finally {
            closeQuietly(configurationInputStream);
        }
    }

    /**
     * @param properties A properties map
     * @return The given properties as a reader to a properties file
     * @throws IOException if a problem occurs opening the reader
     */
    protected Reader getPropertiesAsFile(Properties properties) throws IOException {
        Writer propertiesFileWriter = new StringWriter();
        properties.store(new WriterOutputStream(propertiesFileWriter), null);
        return new StringReader(propertiesFileWriter.toString());
    }

    /**
     * Creates the jar containing the scripts and stores it in the file with the given file name
     *
     * @param jarFile Path where the jar file is stored
     */
    public void writeToJarFile(File jarFile) {
        JarOutputStream jarOutputStream = null;

        try {
            jarOutputStream = new JarOutputStream(new FileOutputStream(jarFile));
            Reader propertiesAsFile = getPropertiesAsFile(getJarProperties());
            writeJarEntry(jarOutputStream, LOCATION_PROPERTIES_FILENAME, System.currentTimeMillis(),
                    propertiesAsFile);
            propertiesAsFile.close();
            for (Script script : getScripts()) {
                Reader scriptContentReader = null;
                try {
                    scriptContentReader = script.getScriptContentHandle().openScriptContentReader();
                    writeJarEntry(jarOutputStream, script.getFileName(), script.getFileLastModifiedAt(),
                            scriptContentReader);
                } finally {
                    closeQuietly(scriptContentReader);
                }
            }
        } catch (IOException e) {
            throw new MigrateBirdException("Error while writing archive file " + jarFile, e);
        } finally {
            closeQuietly(jarOutputStream);
        }
    }

    /**
     * @return The jar location's configuration as a <code>Properties</code> object
     */
    protected Properties getJarProperties() {
        Properties configuration = new Properties();
        configuration.put(PROPERTY_SCRIPT_ENCODING, scriptEncoding);
        configuration.put(PROPERTY_POSTPROCESSINGSCRIPT_DIRNAME, postProcessingScriptDirName);
        configuration.put(PROPERTY_QUALIFIERS, toQualifiersPropertyValue(registeredQualifiers));
        configuration.put(PROPERTY_SCRIPT_PATCH_QUALIFIERS, toQualifiersPropertyValue(patchQualifiers));
        configuration.put(PROPERTY_SCRIPT_INDEX_REGEXP, scriptIndexRegexp);
        configuration.put(PROPERTY_SCRIPT_QUALIFIER_REGEXP, qualifierRegexp);
        configuration.put(PROPERTY_SCRIPT_TARGETDATABASE_REGEXP, targetDatabaseRegexp);
        configuration.put(PROPERTY_SCRIPT_FILE_EXTENSIONS, StringUtils.join(scriptFileExtensions, ","));
        if (baseLineRevision != null) {
            configuration.put(PROPERTY_BASELINE_REVISION, baseLineRevision.getIndexesString());
        }
        configuration.put(PROPERTY_IGNORE_CARRIAGE_RETURN_WHEN_CALCULATING_CHECK_SUM,
                Boolean.toString(ignoreCarriageReturnsWhenCalculatingCheckSum));
        return configuration;
    }

    /**
     * Writes the entry with the given name and content to the given {@link JarOutputStream}
     *
     * @param jarOutputStream    {@link OutputStream} to the jar file
     * @param name               Name of the jar file entry
     * @param timestamp          Last modification date of the entry
     * @param entryContentReader Reader giving access to the content of the jar entry
     * @throws IOException In case of disk IO problems
     */
    protected void writeJarEntry(JarOutputStream jarOutputStream, String name, long timestamp,
            Reader entryContentReader) throws IOException {
        JarEntry jarEntry = new JarEntry(name);
        jarEntry.setTime(timestamp);
        jarOutputStream.putNextEntry(jarEntry);

        InputStream scriptInputStream = new ReaderInputStream(entryContentReader);
        byte[] buffer = new byte[1024];
        int len;
        while ((len = scriptInputStream.read(buffer, 0, buffer.length)) > -1) {
            jarOutputStream.write(buffer, 0, len);
        }
        scriptInputStream.close();
        jarOutputStream.closeEntry();
    }

    protected JarFile createJarFile(File jarFile) {
        try {
            File jarFileWithoutSubPath = getJarFileWithoutSubPath(jarFile);
            return new JarFile(jarFileWithoutSubPath);
        } catch (IOException e) {
            throw new MigrateBirdException("Error opening jar file " + jarFile, e);
        }
    }

    /**
     * Gets the optional sub path in the jar file.
     * E.g. dir/my_archive.jar!subpath/bla => returns subpath/bla/
     *
     * @param jarFile The jar file, not null
     * @return the sub-path ending with /, null if there is no sub-path
     */
    protected String getJarSubPath(File jarFile) {
        String jarFilePath = jarFile.getPath();
        int index = jarFilePath.lastIndexOf('!');
        if (index < 0 || index + 1 == jarFilePath.length()) {
            return null;
        }
        String subPath = jarFilePath.substring(index + 1);
        if (!subPath.endsWith("/")) {
            subPath += "/";
        }
        return subPath;
    }

    /**
     * Gets jar file with the sub-path stripped off
     * E.g. dir/my_archive.jar!subpath/bla => returns dir/my_archive.jar
     *
     * @param jarFile The jar file, not null
     * @return the jar file without sub-path, not null
     */
    protected File getJarFileWithoutSubPath(File jarFile) {
        String jarFilePath = jarFile.getPath();
        int index = jarFilePath.lastIndexOf('!');
        if (index < 0) {
            return jarFile;
        }
        return new File(jarFilePath.substring(0, index));
    }

}