Skip to content

Commit

Permalink
Refactors CalculateChecksumCommandStep (#3696)
Browse files Browse the repository at this point in the history
* Migrates code from Liquibase to TagExists Command step
* Documentation + tests
  • Loading branch information
filipelautert committed Jan 20, 2023
1 parent ccdf86f commit 3cf2402
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 75 deletions.
60 changes: 22 additions & 38 deletions liquibase-core/src/main/java/liquibase/Liquibase.java
Expand Up @@ -7,10 +7,7 @@
import liquibase.changelog.visitor.*;
import liquibase.command.CommandResults;
import liquibase.command.CommandScope;
import liquibase.command.core.DbUrlConnectionCommandStep;
import liquibase.command.core.InternalDropAllCommandStep;
import liquibase.command.core.TagCommandStep;
import liquibase.command.core.TagExistsCommandStep;
import liquibase.command.core.*;
import liquibase.database.Database;
import liquibase.database.DatabaseConnection;
import liquibase.database.DatabaseFactory;
Expand Down Expand Up @@ -55,7 +52,10 @@
import liquibase.util.StringUtil;

import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.Writer;
import java.text.DateFormat;
import java.util.*;
import java.util.function.Supplier;
Expand All @@ -69,10 +69,6 @@
public class Liquibase implements AutoCloseable {

private static final Logger LOG = Scope.getCurrentScope().getLog(Liquibase.class);
protected static final int CHANGESET_ID_NUM_PARTS = 3;
protected static final int CHANGESET_ID_AUTHOR_PART = 2;
protected static final int CHANGESET_ID_CHANGESET_PART = 1;
protected static final int CHANGESET_ID_CHANGELOG_PART = 0;
private static final ResourceBundle coreBundle = getBundle("liquibase/i18n/liquibase-core");
public static final String MSG_COULD_NOT_RELEASE_LOCK = coreBundle.getString("could.not.release.lock");

Expand Down Expand Up @@ -2202,40 +2198,28 @@ public void run() throws Exception {
});
}

/**
* Calculate the checksum for a given identifier
*
* @deprecated Use {link {@link CommandScope(String)}.
*/
public final CheckSum calculateCheckSum(final String changeSetIdentifier) throws LiquibaseException {
if (changeSetIdentifier == null) {
throw new LiquibaseException(new IllegalArgumentException("changeSetIdentifier"));
}
final List<String> parts = StringUtil.splitAndTrim(changeSetIdentifier, "::");
if ((parts == null) || (parts.size() < CHANGESET_ID_NUM_PARTS)) {
throw new LiquibaseException(
new IllegalArgumentException("Invalid changeSet identifier: " + changeSetIdentifier)
);
}
return this.calculateCheckSum(parts.get(CHANGESET_ID_CHANGELOG_PART),
parts.get(CHANGESET_ID_CHANGESET_PART), parts.get(CHANGESET_ID_AUTHOR_PART));
CommandResults commandResults = new CommandScope("calculateChecksum")
.addArgumentValue(DbUrlConnectionCommandStep.DATABASE_ARG, database)
.addArgumentValue(CalculateChecksumCommandStep.CHANGESET_IDENTIFIER_ARG, changeSetIdentifier)
.addArgumentValue(CalculateChecksumCommandStep.CHANGELOG_FILE_ARG, this.changeLogFile)
.execute();
return commandResults.getResult(CalculateChecksumCommandStep.CHECKSUM_RESULT);
}

/**
* Calculates the checksum for the values that form a given identifier
*
* @deprecated Use {link {@link CommandScope(String)}.
*/
public CheckSum calculateCheckSum(final String filename, final String id, final String author)
throws LiquibaseException {
LOG.info(String.format("Calculating checksum for changeset %s::%s::%s", filename, id, author));
final ChangeLogParameters clParameters = this.getChangeLogParameters();
final ResourceAccessor resourceAccessor = this.getResourceAccessor();
final DatabaseChangeLog changeLog =
ChangeLogParserFactory.getInstance().getParser(
this.changeLogFile, resourceAccessor
).parse(this.changeLogFile, clParameters, resourceAccessor);

// TODO: validate?

final ChangeSet changeSet = changeLog.getChangeSet(filename, author, id);
if (changeSet == null) {
throw new LiquibaseException(
new IllegalArgumentException("No such changeSet: " + filename + "::" + id + "::" + author)
);
}

return changeSet.generateCheckSum();
return this.calculateCheckSum(String.format("%s::%s::%s", filename, id, author));
}

public void generateDocumentation(String outputDirectory) throws LiquibaseException {
Expand Down
@@ -1,56 +1,98 @@
package liquibase.command.core;

import liquibase.Scope;
import liquibase.change.CheckSum;
import liquibase.changelog.ChangeLogParameters;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.command.*;
import liquibase.configuration.ConfigurationValueObfuscator;
import liquibase.exception.CommandExecutionException;
import liquibase.database.Database;
import liquibase.exception.LiquibaseException;
import liquibase.parser.ChangeLogParserFactory;
import liquibase.resource.ResourceAccessor;
import liquibase.util.StringUtil;

public class CalculateChecksumCommandStep extends AbstractCliWrapperCommandStep {
import java.util.Arrays;
import java.util.List;

public static String[] COMMAND_NAME = {"calculateChecksum"};
public class CalculateChecksumCommandStep extends AbstractCommandStep {

protected static final String[] COMMAND_NAME = {"calculateChecksum"};

public static final CommandArgumentDefinition<String> CHANGELOG_FILE_ARG;
public static final CommandArgumentDefinition<String> CHANGESET_IDENTIFIER_ARG;
public static final CommandArgumentDefinition<String> URL_ARG;
public static final CommandArgumentDefinition<String> DEFAULT_SCHEMA_NAME_ARG;
public static final CommandArgumentDefinition<String> DEFAULT_CATALOG_NAME_ARG;
public static final CommandArgumentDefinition<String> USERNAME_ARG;
public static final CommandArgumentDefinition<String> PASSWORD_ARG;
public static final CommandArgumentDefinition<String> DRIVER_ARG;
public static final CommandArgumentDefinition<String> DRIVER_PROPERTIES_FILE_ARG;

public static final CommandResultDefinition<CheckSum> CHECKSUM_RESULT;

protected static final int CHANGESET_ID_NUM_PARTS = 3;
protected static final int CHANGESET_ID_AUTHOR_PART = 2;
protected static final int CHANGESET_ID_CHANGESET_PART = 1;
protected static final int CHANGESET_ID_CHANGELOG_PART = 0;


static {
CommandBuilder builder = new CommandBuilder(COMMAND_NAME);
CHANGELOG_FILE_ARG = builder.argument(CommonArgumentNames.CHANGELOG_FILE, String.class).required()
.description("The root changelog file").build();
URL_ARG = builder.argument(CommonArgumentNames.URL, String.class).required()
.description("The JDBC database connection URL").build();
DEFAULT_SCHEMA_NAME_ARG = builder.argument("defaultSchemaName", String.class)
.description("The default schema name to use for the database connection").build();
DEFAULT_CATALOG_NAME_ARG = builder.argument("defaultCatalogName", String.class)
.description("The default catalog name to use for the database connection").build();
DRIVER_ARG = builder.argument("driver", String.class)
.description("The JDBC driver class").build();
DRIVER_PROPERTIES_FILE_ARG = builder.argument("driverPropertiesFile", String.class)
.description("The JDBC driver properties file").build();
USERNAME_ARG = builder.argument(CommonArgumentNames.USERNAME, String.class)
.description("The database username").build();
PASSWORD_ARG = builder.argument(CommonArgumentNames.PASSWORD, String.class)
.description("The database password")
.setValueObfuscator(ConfigurationValueObfuscator.STANDARD)
.build();
CHANGESET_IDENTIFIER_ARG = builder.argument("changesetIdentifier", String.class).required()
.description("Changeset ID identifier of form filepath::id::author").build();

CHECKSUM_RESULT = builder.result("checksumResult", CheckSum.class).description("Calculated checksum").build();
}

@Override
public List<Class<?>> requiredDependencies() {
return Arrays.asList(Database.class);
}

@Override
public String[][] defineCommandNames() {
return new String[][] { COMMAND_NAME };
}


@Override
protected String[] collectArguments(CommandScope commandScope) throws CommandExecutionException {
return collectArguments(commandScope, null, CHANGESET_IDENTIFIER_ARG.getName());
public void run(CommandResultsBuilder resultsBuilder) throws Exception {
CommandScope commandScope = resultsBuilder.getCommandScope();
final String changeSetIdentifier = commandScope.getArgumentValue(CHANGESET_IDENTIFIER_ARG);
final String changeLogFile = commandScope.getArgumentValue(CHANGELOG_FILE_ARG).replace('\\', '/');
final Database database = (Database) commandScope.getDependency(Database.class);

List<String> parts = validateAndExtractParts(changeSetIdentifier, changeLogFile);
Scope.getCurrentScope().getLog(getClass()).info(String.format("Calculating checksum for changeset %s", changeSetIdentifier));

ResourceAccessor resourceAccessor = Scope.getCurrentScope().getResourceAccessor();
DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(
changeLogFile, resourceAccessor).parse(changeLogFile, new ChangeLogParameters(database), resourceAccessor);

ChangeSet changeSet = changeLog.getChangeSet(parts.get(CHANGESET_ID_CHANGELOG_PART), parts.get(CHANGESET_ID_AUTHOR_PART),
parts.get(CHANGESET_ID_CHANGESET_PART));
if (changeSet == null) {
throw new LiquibaseException(new IllegalArgumentException("No such changeSet: " + changeSetIdentifier));
}
sendMessages(resultsBuilder, changeSet.generateCheckSum());
}

private static void sendMessages(CommandResultsBuilder resultsBuilder, CheckSum checkSum) {
resultsBuilder.addResult(CHECKSUM_RESULT, checkSum);
Scope.getCurrentScope().getUI().sendMessage(checkSum.toString());
}

private List<String> validateAndExtractParts(String changeSetIdentifier, String changeLogFile) throws LiquibaseException {
if (StringUtil.isEmpty(changeSetIdentifier)) {
throw new LiquibaseException(new IllegalArgumentException(CHANGESET_IDENTIFIER_ARG.getName()));
}

if (StringUtil.isEmpty(changeLogFile)) {
throw new LiquibaseException(new IllegalArgumentException(CHANGELOG_FILE_ARG.getName()));
}

final List<String> parts = StringUtil.splitAndTrim(changeSetIdentifier, "::");
if ((parts == null) || (parts.size() < CHANGESET_ID_NUM_PARTS)) {
throw new LiquibaseException(
new IllegalArgumentException("Invalid changeSet identifier: " + changeSetIdentifier)
);
}
return parts;
}

@Override
Expand Down
Expand Up @@ -1793,9 +1793,7 @@ protected void doMigration() throws Exception {
liquibase.clearCheckSums();
return;
} else if (COMMANDS.CALCULATE_CHECKSUM.equalsIgnoreCase(command)) {
CheckSum checkSum = null;
checkSum = liquibase.calculateCheckSum(commandParams.iterator().next());
Scope.getCurrentScope().getUI().sendMessage(checkSum.toString());
liquibase.calculateCheckSum(commandParams.iterator().next());
return;
} else if (COMMANDS.DB_DOC.equalsIgnoreCase(command)) {
if (commandParams.isEmpty()) {
Expand Down
Expand Up @@ -23,10 +23,10 @@ Optional Args:
Default: null
driverPropertiesFile (String) The JDBC driver properties file
Default: null
password (String) The database password
password (String) Password to use to connect to the database
Default: null
OBFUSCATED
username (String) The database username
username (String) Username to use to connect to the database
Default: null
"""

Expand All @@ -40,7 +40,7 @@ Optional Args:
]

expectedResults = [
statusCode : 0
checksumResult : "8:b6084e5d5f46b534bbbe18a0d35d34e0"
]
}

Expand All @@ -50,6 +50,7 @@ Optional Args:
]

expectedException = CommandValidationException.class
expectedExceptionMessage = 'Invalid argument \'changelogFile\': missing required argument'
}

run "Run without changesetIdentifier should throw an exception", {
Expand All @@ -58,14 +59,17 @@ Optional Args:
]

expectedException = CommandValidationException.class
expectedExceptionMessage = "Invalid argument \'changesetIdentifier\': missing required argument"
}

run "Run without URL should throw an exception", {
arguments = [
url: "",
changelogFile : "changelogs/h2/complete/rollback.tag.changelog.xml"
changelogFile : "changelogs/h2/complete/rollback.tag.changelog.xml",
changesetIdentifier: "changelogs/h2/complete/rollback.tag.changelog.xml::1::nvoxland",
]

expectedException = CommandValidationException.class
expectedExceptionMessage = "Invalid argument \'url\': missing required argument"
}
}

0 comments on commit 3cf2402

Please sign in to comment.