io.stallion.dataAccess.db.SqlMigrationAction.java Source code

Java tutorial

Introduction

Here is the source code for io.stallion.dataAccess.db.SqlMigrationAction.java

Source

/*
 * Stallion Core: A Modern Web Framework
 *
 * Copyright (C) 2015 - 2016 Stallion Software LLC.
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 2 of
 * the License, or (at your option) any later version. This program 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 General Public
 * License for more details. You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
 *
 *
 *
 */

package io.stallion.dataAccess.db;

import io.stallion.boot.AppContextLoader;
import io.stallion.boot.StallionRunAction;
import io.stallion.boot.SqlMigrateCommandOptions;
import io.stallion.exceptions.ConfigException;
import io.stallion.plugins.PluginRegistry;
import io.stallion.plugins.StallionJavaPlugin;
import io.stallion.reflection.PropertyComparator;
import io.stallion.services.Log;
import io.stallion.utils.ResourceHelpers;
import io.stallion.utils.json.JSON;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.parboiled.common.FileUtils;

import javax.script.ScriptEngine;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static io.stallion.utils.Literals.*;
import static io.stallion.Context.*;

/**
 * Runs all unexecuted database migrations.
 */
public class SqlMigrationAction implements StallionRunAction<SqlMigrateCommandOptions> {
    private ScriptEngine scriptEngine;

    @Override
    public String getActionName() {
        return "sql-migrate";
    }

    @Override
    public String getHelp() {
        return "Executes all SQL scripts in the sql folder that are above the current migration number";
    }

    @Override
    public SqlMigrateCommandOptions newCommandOptions() {
        return new SqlMigrateCommandOptions();
    }

    @Override
    public void loadApp(SqlMigrateCommandOptions options) {
        AppContextLoader.loadWithSettingsOnly(options);
        DB.load();
    }

    @Override
    public void execute(SqlMigrateCommandOptions options) throws Exception {
        createMigrationTrackingTableIfNotExists();
        // Get a ticket to make sure the tickets table is operational
        Long nonce = DB.instance().getTickets().nextId();
        DB db = DB.instance();
        for (SqlMigration migration : findNotRunMigrations()) {
            Log.info("Run migration app:" + migration.getAppName() + " file:" + migration.getFilename());
            if (migration.isJavascript()) {
                ScriptEngine engine = getOrCreateScriptEngine();
                ;
                engine.put("db", db);
                engine.eval("load(" + JSON.stringify(map(val("script", transformJavascript(migration.getSource())),
                        val("name", migration.getFilename()))) + ");");
            } else {
                db.execute(migration.getSource());

            }
            db.execute(
                    "INSERT INTO stallion_sql_migrations (versionNumber, appName, fileName, executedAt) VALUES(?, ?, ?, UTC_TIMESTAMP())",
                    migration.getVersionNumber(), migration.getAppName(), migration.getFilename());
        }
    }

    public List<SqlMigration> findNotRunMigrations() {
        List<SqlMigration> migrations = getDefaultMigrations();
        migrations.addAll(getUserMigrations());
        DB db = DB.instance();
        List<SqlMigration> unRunMigrations = list();
        for (SqlMigration migration : migrations) {
            Long currentVersion = or(
                    db.queryScalar("SELECT MAX(versionNumber) FROM stallion_sql_migrations WHERE appName=?",
                            migration.getAppName()),
                    0L);
            if (currentVersion >= migration.getVersionNumber()) {
                Log.finer("File {0} is below current version of {1} for app {2}", migration.getFilename(),
                        currentVersion, migration.getAppName());
                continue;
            }
            unRunMigrations.add(migration);
        }
        return unRunMigrations;
    }

    public String transformJavascript(String source) {
        StringBuilder builder = new StringBuilder();
        int current = 0;
        int end = source.length() - 1;
        for (int i : safeLoop(1000)) {
            int start = source.indexOf("'''", current);
            if (start == -1) {
                break;
            }
            int last = source.indexOf("'''", start + 3);
            if (last == -1) {
                break;
            }
            builder.append(source.substring(current, start));
            String inner = source.substring(start + 3, last);
            builder.append(JSON.stringify(inner));
            current = last + 3;
        }
        builder.append(source.substring(current, end));
        return builder.toString();
    }

    public ScriptEngine getOrCreateScriptEngine() {
        if (scriptEngine == null) {
            scriptEngine = new NashornScriptEngineFactory().getScriptEngine();
        }
        return scriptEngine;
    }

    public List<SqlMigration> getUserMigrations() {
        List<SqlMigration> migrations = list();
        File sqlDirectory = new File(settings().getTargetFolder() + "/sql");
        if (!sqlDirectory.isDirectory()) {
            Log.finer("Sql directory does not exist {0}", sqlDirectory.getAbsoluteFile());
            return migrations;
        }
        Log.finer("Find sql files in {0}", sqlDirectory.getAbsolutePath());
        for (File file : sqlDirectory.listFiles()) {
            Log.finer("Scan file" + file.getAbsolutePath());
            if (!set("js", "sql").contains(FilenameUtils.getExtension(file.getAbsolutePath()))) {
                Log.finer("Extension is not .js or .sql {0}", file.getAbsolutePath());
                continue;
            }
            if (file.getName().startsWith(".") || file.getName().startsWith("#")) {
                Log.finer("File name starts with invalid character {0}", file.getName());
                continue;
            }
            if (!file.getName().contains("." + DB.instance().getDbImplementation().getName().toLowerCase() + ".")) {
                Log.finer("File name does not contain the name of the current database engine: \".{0}.\"",
                        DB.instance().getDbImplementation().getName().toLowerCase());
                continue;
            }
            if (!file.getName().contains("-")) {
                Log.finer("File name does not have version part {0}", file.getName());
                continue;
            }
            String versionString = StringUtils.stripStart(StringUtils.split(file.getName(), "-")[0], "0");
            if (!StringUtils.isNumeric(versionString)) {
                Log.finer("File name does not have numeric version part {0}", file.getName());
                continue;
            }
            Log.info("Load SQL file for migration: {0}", file.getName());

            migrations.add(new SqlMigration()
                    .setVersionNumber(Integer.parseInt(StringUtils.stripStart(versionString, "0"))).setAppName("")
                    .setFilename(file.getName()).setSource(FileUtils.readAllText(file)));
        }
        migrations.sort(new PropertyComparator<>("versionNumber"));
        return migrations;
    }

    public List<SqlMigration> getDefaultMigrations() {
        List<SqlMigration> migrations = list();
        Map<String, List<String>> pluginMigrations = new LinkedHashMap<>();
        pluginMigrations.put("stallion",
                list("00004-users", "00006-async_tasks", "00010-job_status", "00011-temp_tokens",
                        "00020-create-audit-trail", "00025-create-transaction-log", "00030-job-status-new-columns",
                        "00035-uploaded-files", "00040-short_code_tokens", "00050-dynamic_settings"));
        if (PluginRegistry.instance() != null) {
            for (StallionJavaPlugin plugin : PluginRegistry.instance().getJavaPluginByName().values()) {
                pluginMigrations.put(plugin.getPluginName(), plugin.getSqlMigrations());
            }
        }
        for (Map.Entry<String, List<String>> entry : pluginMigrations.entrySet()) {
            for (String fileStub : entry.getValue()) {
                Log.info("fileStub: {0}", fileStub);
                int version = Integer.parseInt(fileStub.split("\\-")[0]);
                String fileName = fileStub + "." + DB.instance().getDbImplementation().getName().toLowerCase()
                        + ".sql";
                if (!ResourceHelpers.resourceExists(entry.getKey(), "/sql/" + fileName)) {
                    fileName = fileStub + "." + DB.instance().getDbImplementation().getName().toLowerCase() + ".js";
                }
                if (!ResourceHelpers.resourceExists(entry.getKey(), "/sql/" + fileName)) {
                    throw new ConfigException("The sql migration file /sql/" + fileName
                            + " does not exist in plugin " + entry.getKey());
                }
                String source = ResourceHelpers.loadResource(entry.getKey(), "/sql/" + fileName);
                SqlMigration migration = new SqlMigration().setAppName(entry.getKey()).setFilename(fileName)
                        .setSource(source).setVersionNumber(version);
                migrations.add(migration);
            }
        }

        return migrations;
    }

    void createMigrationTrackingTableIfNotExists() {
        if (DB.instance() == null) {
            throw new ConfigException("No database available. Did you configure a database in stallion.toml?");
        }
        String sql = ResourceHelpers.loadResource("stallion",
                "/sql/migrations-table." + DB.instance().getDbImplementation().getName() + ".sql");
        DB.instance().execute(sql);

    }
}