package net.raymanoz.command;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.raymanoz.config.Configuration;
import net.raymanoz.config.ScriptStatus;
import net.raymanoz.domain.SchemaVersion;
import net.raymanoz.io.File;
import net.raymanoz.migrate.SchemaVersionRepository;
import net.raymanoz.migrate.Script;
import net.raymanoz.migrate.ScriptList;
import net.raymanoz.ui.UserInteractionStrategy;
import net.raymanoz.util.ScriptCalculator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class MigrateCommand implements Command, MigrateCallBack {
final public static String[] OVERRIDE_PROP_SWITCH_NAMES = new String[]{"properties", "p"};
private static final Log LOG = LogFactory.getLog(MigrateCommand.class);
static final String COMMAND_STRING = "migrate";
static class Helper {
long scriptsToBeApplied(SchemaVersion version, ScriptList scriptlist){
return new ScriptCalculator(version, scriptlist).scriptsToBeApplied();
}
}
private final CommandAssembler assembler;
private final SchemaVersionRepository repository;
private final Configuration configuration;
private final UserInteractionStrategy interactionStrategy;
private final Helper helper;
MigrateCommand(SchemaVersionRepository repository, Configuration configuration, CommandAssembler assembler, Helper helper) {
this.repository = repository;
this.configuration = configuration;
this.assembler = assembler;
this.interactionStrategy = configuration.getUserInteractionStrategy();
this.helper = helper;
}
public MigrateCommand(SchemaVersionRepository repository, Configuration configuration, CommandAssembler assembler) {
this(repository, configuration, assembler, new Helper());
}
public void execute(String[] args) {
execute();
}
public void execute() {
if (interactionStrategy.startUIProcess(this)) return;
summary();
doMigration();
}
private ArrayList<ScriptList> scripts = new ArrayList<ScriptList>();
private SchemaVersion version;
private int scriptsToBeApplied = -1;
SchemaVersion version(){
if (version == null) version = repository.getVersion();
return version;
}
List<ScriptList> scripts(){
if (scripts.size() > 0) return scripts;
version = version();
repository.validateNoOtherLaterActivity(version);
for (long dbver = version.getDBVersion(); dbver <= configuration.getLatestDBVersion(); dbver++) {
File scriptsDir = configuration.getScriptDirectory(dbver);
ScriptList scriptlist = assembler.newScriptList(scriptsDir.listFiles(), dbver);
scripts.add(scriptlist);
}
return scripts;
}
final static String VERSION_LOG_MESSAGE_FMT = "%d scripts found for version %d, %d to be applied";
final static String ALL_VERSIONS_LOG_MESSAGE_FMT = "%d scripts found for %d versions, %d to be applied";
final static String STARTUP_STATUS_MESSAGE_FMT = "Schema DB version at startup: %d patch %d";
final static String COMPLETED_STATUS_MESSAGE_FMT = "Schema DB version now: %d patch %d";
private void logAndAppend(StringBuilder builder, String message){
if (message == null||message.trim().isEmpty()) return;
LOG.info(message);
builder.append(message);
builder.append("\n");
}
private void logAndAppend(StringBuilder builder, String messageFmt, Object ... args){
logAndAppend(builder, String.format(messageFmt, args));
}
public String summary(){
StringBuilder builder = new StringBuilder();
LOG.info(configuration.getMigrationMessage());
SchemaVersion ver = version();
logAndAppend(builder, STARTUP_STATUS_MESSAGE_FMT, ver.getDBVersion(), ver.getPatchNo());
scriptsToBeApplied = 0;
long totalScripts = 0;
for (ScriptList scriptlist: scripts()){
totalScripts += scriptlist.size();
long thisToApplied = helper.scriptsToBeApplied(ver, scriptlist);
scriptsToBeApplied += thisToApplied;
logAndAppend(builder, VERSION_LOG_MESSAGE_FMT, scriptlist.size(), scriptlist.DBVersion(), thisToApplied);
}
if (scripts.size() != 1) {
logAndAppend(builder, ALL_VERSIONS_LOG_MESSAGE_FMT, totalScripts, scripts.size(), scriptsToBeApplied);
}
return builder.toString();
}
final static String SCRIPT_APPLICATION_STATUS_MESSAGE_FMT = "script (%d of %d): %s - %s";
@Override
public int noScriptsToMigrate() {
if (scriptsToBeApplied < 0) summary();
return scriptsToBeApplied;
}
private void logAndSendStatus(ScriptStatus status, Script script){
LOG.info(status + ", " + script.description());
interactionStrategy.scriptStatusMessage(scriptIdx, script, status);
}
private int scriptIdx = 0;
private int scriptsApplied = 0;
private int scriptsSkipped = 0;
boolean alreadyMigrated(Script script){
final long thisPatchNo = script.getPatch();
final long thisDBver = script.getDBVersion();
return (thisDBver < version().getDBVersion() || thisDBver == version().getDBVersion() && thisPatchNo <= version().getPatchNo());
}
boolean migrate(Script script){
if (alreadyMigrated(script)) return true;
final long thisPatchNo = script.getPatch();
final long thisDBver = script.getDBVersion();
SchemaVersion version = version();
repository.validateNoOtherActivity(thisDBver, thisPatchNo);
scriptIdx++;
logAndSendStatus(ScriptStatus.STARTED, script);
repository.recordStartPatch(script);
ScriptStatus status = script.execute(interactionStrategy);
repository.recordFinishPatch(script, status);
logAndSendStatus(status, script);
version.setDBVersion(thisDBver);
version.setPatchNo(thisPatchNo);
switch (status){
case COMPLETED:
scriptsApplied++;
return true;
case SKIPPED:
scriptsSkipped++;
return true;
}
return false;
}
private boolean migrateScripts(){
for (ScriptList scriptlist : scripts()) {
for (Script script : scriptlist) {
if (!migrate(script)) return false;
}
}
return true;
}
void closeConnection(){
try {
configuration.getConnection().close();
} catch (Exception e) {
LOG.error(e);
interactionStrategy.errorMessage("Closing connection threw: " + e.getMessage());
}
}
public void doMigration() {
boolean allMigrated = false;
scriptIdx = 0;
scriptsApplied = 0;
scriptsSkipped = 0;
final StringBuilder builder = new StringBuilder();
SchemaVersion ver = version();
final String startUpState = String.format(STARTUP_STATUS_MESSAGE_FMT, ver.getDBVersion(), ver.getPatchNo());
try {
allMigrated = migrateScripts();
ver = repository.getVersion();
logAndAppend(builder, startUpState);
logAndAppend(builder, COMPLETED_STATUS_MESSAGE_FMT, ver.getDBVersion(), ver.getPatchNo());
} finally {
closeConnection();
logAndAppend(builder, "%d script%s applied", scriptsApplied, ((scriptsApplied!=1)?"s":""));
if (scriptsSkipped > 0) {
logAndAppend(builder, "%d script%s skipped", scriptsSkipped, ((scriptsSkipped!=1)?"s":""));
logAndAppend(builder, "%d script%s in total", scriptIdx, ((scriptIdx !=1)?"s":""));
}
interactionStrategy.completed(builder.toString(), allMigrated);
}
}
void addPropertiesNeedingValues(Script script, Set<String> variables){
if (alreadyMigrated(script)) return;
variables.addAll(script.variablesRequiringDialog());
}
@Override
public Set<String> variablesRequiringDialogInOutstanding() {
Set<String> result = new HashSet<String>();
for (ScriptList scriptlist : scripts()) {
for (Script script : scriptlist) {
addPropertiesNeedingValues(script, result);
}
}
return result;
}
public String getCommandString() {
return COMMAND_STRING;
}
@Override
public String[] helpMessage() {
return new String[]{
"[--" + OVERRIDE_PROP_SWITCH_NAMES[0] + " override.properties]",
"Performs a migration to the top revision",
"when override.properties is specified this is searched first for ",
"any configuration values. If a value is not found in ",
"override.properties, uMigrate.properties will then be checked"
};
}
}