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

liquibase.parser.AbstractFormattedChangeLogParser Maven / Gradle / Ivy

There is a newer version: 4.30.0
Show newest version
package liquibase.parser;

import liquibase.Labels;
import liquibase.Scope;
import liquibase.change.AbstractSQLChange;
import liquibase.change.Change;
import liquibase.change.core.EmptyChange;
import liquibase.changelog.ChangeLogParameters;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.changeset.ChangeSetService;
import liquibase.changeset.ChangeSetServiceFactory;
import liquibase.exception.ChangeLogParseException;
import liquibase.exception.LiquibaseException;
import liquibase.resource.ResourceAccessor;
import liquibase.util.StreamUtil;
import liquibase.util.StringUtil;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.ResourceBundle.getBundle;

@SuppressWarnings("java:S2583")
public abstract class AbstractFormattedChangeLogParser implements ChangeLogParser {

    private static final ResourceBundle coreBundle = getBundle("liquibase/i18n/liquibase-core");
    private static final String EXCEPTION_MESSAGE = coreBundle.getString("formatted.changelog.exception.message");

    protected final String FIRST_LINE_REGEX = String.format("^\\s*%s\\s*liquibase\\s*formatted.*", getSingleLineCommentSequence());
    protected final Pattern FIRST_LINE_PATTERN = Pattern.compile(FIRST_LINE_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String PROPERTY_REGEX = String.format("\\s*%s[\\s]*property\\s+(.*:.*)\\s+(.*:.*).*", getSingleLineCommentSequence());
    protected final Pattern PROPERTY_PATTERN = Pattern.compile(PROPERTY_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ALT_PROPERTY_ONE_CHARACTER_REGEX = String.format("\\s*?[%s]+\\s*property\\s.*", getSingleLineCommentOneCharacter());

    protected final Pattern ALT_PROPERTY_ONE_CHARACTER_PATTERN = Pattern.compile(ALT_PROPERTY_ONE_CHARACTER_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String CHANGE_SET_REGEX = String.format("\\s*%s[\\s]*changeset\\s+(\"[^\"]+\"|[^:]+):\\s*(\"[^\"]+\"|\\S+).*", getSingleLineCommentSequence());
    protected final Pattern CHANGE_SET_PATTERN = Pattern.compile(CHANGE_SET_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ALT_CHANGE_SET_ONE_CHARACTER_REGEX = String.format("%s[\\s]*changeset\\s.*", getSingleLineCommentOneCharacter());
    protected final Pattern ALT_CHANGE_SET_ONE_CHARACTER_PATTERN = Pattern.compile(ALT_CHANGE_SET_ONE_CHARACTER_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ALT_CHANGE_SET_NO_OTHER_INFO_REGEX = String.format("\\s*%s[\\s]*changeset[\\s]*.*$", getSingleLineCommentSequence());
    protected final Pattern ALT_CHANGE_SET_NO_OTHER_INFO_PATTERN = Pattern.compile(ALT_CHANGE_SET_NO_OTHER_INFO_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ROLLBACK_REGEX = String.format("\\s*%s[\\s]*rollback (.*)", getSingleLineCommentSequence());
    protected final Pattern ROLLBACK_PATTERN = Pattern.compile(ROLLBACK_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ALT_ROLLBACK_ONE_CHARACTER_REGEX = String.format("\\s*%s[\\s]*rollback\\s.*", getSingleLineCommentOneCharacter());
    protected final Pattern ALT_ROLLBACK_ONE_CHARACTER_PATTERN = Pattern.compile(ALT_ROLLBACK_ONE_CHARACTER_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String PRECONDITIONS_REGEX = String.format("\\s*%s[\\s]*preconditions(.*)", getSingleLineCommentSequence());
    protected final Pattern PRECONDITIONS_PATTERN = Pattern.compile(PRECONDITIONS_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ALT_PRECONDITIONS_ONE_CHARACTER_REGEX = String.format("\\s*%s[\\s]*preconditions\\s.*", getSingleLineCommentOneCharacter());
    protected final Pattern ALT_PRECONDITIONS_ONE_CHARACTER_PATTERN = Pattern.compile(ALT_PRECONDITIONS_ONE_CHARACTER_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String PRECONDITION_REGEX = String.format("\\s*%s[\\s]*precondition\\-([a-zA-Z0-9-]+) (.*)", getSingleLineCommentSequence());
    protected final Pattern PRECONDITION_PATTERN = Pattern.compile(PRECONDITION_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ALT_PRECONDITION_ONE_CHARACTER_REGEX = String.format("\\s*%s[\\s]*precondition(.*)", getSingleLineCommentOneCharacter());
    protected final Pattern ALT_PRECONDITION_ONE_CHARACTER_PATTERN = Pattern.compile(ALT_PRECONDITION_ONE_CHARACTER_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String STRIP_COMMENTS_REGEX = ".*stripComments:(\\w+).*";
    protected final Pattern STRIP_COMMENTS_PATTERN = Pattern.compile(STRIP_COMMENTS_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String SPLIT_STATEMENTS_REGEX = ".*splitStatements:(\\w+).*";
    protected final Pattern SPLIT_STATEMENTS_PATTERN = Pattern.compile(SPLIT_STATEMENTS_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String ROLLBACK_SPLIT_STATEMENTS_REGEX = ".*rollbackSplitStatements:(\\w+).*";
    protected final Pattern ROLLBACK_SPLIT_STATEMENTS_PATTERN = Pattern.compile(ROLLBACK_SPLIT_STATEMENTS_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String END_DELIMITER_REGEX = ".*endDelimiter:(\\S*).*";
    protected final Pattern END_DELIMITER_PATTERN = Pattern.compile(END_DELIMITER_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String ROLLBACK_END_DELIMITER_REGEX = ".*rollbackEndDelimiter:(\\S*).*";
    protected final Pattern ROLLBACK_END_DELIMITER_PATTERN = Pattern.compile(ROLLBACK_END_DELIMITER_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String COMMENT_REGEX = String.format("%s[\\s]*comment:? (.*)", getSingleLineCommentSequence());
    protected final Pattern COMMENT_PATTERN = Pattern.compile(COMMENT_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ALT_COMMENT_PLURAL_REGEX = String.format("%s[\\s]*comments:? (.*)", getSingleLineCommentSequence());
    protected final Pattern ALT_COMMENT_PLURAL_PATTERN = Pattern.compile(ALT_COMMENT_PLURAL_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ALT_COMMENT_ONE_CHARACTER_REGEX = String.format("%s[\\s]*comment:? (.*)", getSingleLineCommentOneCharacter());
    protected final Pattern ALT_COMMENT_ONE_CHARACTER_PATTERN = Pattern.compile(ALT_COMMENT_ONE_CHARACTER_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String VALID_CHECK_SUM_REGEX = String.format("%s[\\s]*validCheckSum:? (.*)", getSingleLineCommentSequence());
    protected final Pattern VALID_CHECK_SUM_PATTERN = Pattern.compile(VALID_CHECK_SUM_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ALT_VALID_CHECK_SUM_ONE_CHARACTER_REGEX = String.format("^%s[\\s]*validCheckSum(.*)$", getSingleLineCommentOneCharacter());
    protected final Pattern ALT_VALID_CHECK_SUM_ONE_CHARACTER_PATTERN = Pattern.compile(ALT_VALID_CHECK_SUM_ONE_CHARACTER_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String IGNORE_LINES_REGEX = String.format("%s[\\s]*ignoreLines:(\\w+)", getSingleLineCommentSequence());
    protected final Pattern IGNORE_LINES_PATTERN = Pattern.compile(IGNORE_LINES_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ALT_IGNORE_LINES_ONE_CHARACTER_REGEX = String.format("%s[\\s]*?ignoreLines:(\\w+).*$", getSingleLineCommentOneCharacter());
    protected final Pattern ALT_IGNORE_LINES_ONE_CHARACTER_PATTERN = Pattern.compile(ALT_IGNORE_LINES_ONE_CHARACTER_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ALT_IGNORE_REGEX = String.format("%s[\\s]*ignore:(\\w+)", getSingleLineCommentSequence());
    protected final Pattern ALT_IGNORE_PATTERN = Pattern.compile(ALT_IGNORE_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String RUN_WITH_REGEX = ".*runWith:([\\w\\$\\{\\}]+).*";
    protected final Pattern RUN_WITH_PATTERN = Pattern.compile(RUN_WITH_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String RUN_WITH_SPOOL_FILE_REGEX = ".*runWithSpoolFile:(.*).*";
    protected final Pattern RUN_WITH_SPOOL_FILE_PATTERN = Pattern.compile(RUN_WITH_SPOOL_FILE_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String RUN_ON_CHANGE_REGEX = ".*runOnChange:(\\w+).*";
    protected final Pattern RUN_ON_CHANGE_PATTERN = Pattern.compile(RUN_ON_CHANGE_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String RUN_ALWAYS_REGEX = ".*runAlways:(\\w+).*";
    protected final Pattern RUN_ALWAYS_PATTERN = Pattern.compile(RUN_ALWAYS_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String CONTEXT_REGEX = ".*context:(\".*?\"|\\S*).*";
    protected final Pattern CONTEXT_PATTERN = Pattern.compile(CONTEXT_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String CONTEXT_FILTER_REGEX = ".*contextFilter:(\".*?\"|\\S*).*";
    protected final Pattern CONTEXT_FILTER_PATTERN = Pattern.compile(CONTEXT_FILTER_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String LOGICAL_FILE_PATH_REGEX = ".*logicalFilePath:(\\S*).*";
    protected final Pattern LOGICAL_FILE_PATH_PATTERN = Pattern.compile(LOGICAL_FILE_PATH_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String LABELS_REGEX = ".*labels:(\".*?\"|\\S*).*";
    protected final Pattern LABELS_PATTERN = Pattern.compile(LABELS_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String RUN_IN_TRANSACTION_REGEX = ".*runInTransaction:(\\w+).*";
    protected final Pattern RUN_IN_TRANSACTION_PATTERN = Pattern.compile(RUN_IN_TRANSACTION_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String DBMS_REGEX = ".*dbms:([^,][\\w!,]+).*";
    protected final Pattern DBMS_PATTERN = Pattern.compile(DBMS_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String IGNORE_REGEX = ".*ignore:(\\w*).*";
    protected final Pattern IGNORE_PATTERN = Pattern.compile(IGNORE_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String FAIL_ON_ERROR_REGEX = ".*failOnError:(\\w+).*";
    protected final Pattern FAIL_ON_ERROR_PATTERN = Pattern.compile(FAIL_ON_ERROR_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String ON_FAIL_REGEX = ".*onFail:(\\w+).*";
    protected final Pattern ON_FAIL_PATTERN = Pattern.compile(ON_FAIL_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String ON_ERROR_REGEX = ".*onError:(\\w+).*";
    protected final Pattern ON_ERROR_PATTERN = Pattern.compile(ON_ERROR_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String ROLLBACK_CHANGE_SET_ID_REGEX = ".*changeSetId:(\\S+).*";
    protected final Pattern ROLLBACK_CHANGE_SET_ID_PATTERN = Pattern.compile(ROLLBACK_CHANGE_SET_ID_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String ROLLBACK_CHANGE_SET_AUTHOR_REGEX = ".*changesetAuthor:(\\S+).*";
    protected final Pattern ROLLBACK_CHANGE_SET_AUTHOR_PATTERN = Pattern.compile(ROLLBACK_CHANGE_SET_AUTHOR_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String ROLLBACK_CHANGE_SET_PATH_REGEX = ".*changesetPath:(\\S+).*";
    protected final Pattern ROLLBACK_CHANGE_SET_PATH_PATTERN = Pattern.compile(ROLLBACK_CHANGE_SET_PATH_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ROLLBACK_MULTI_LINE_START_REGEX = String.format("\\s*%s\\s*liquibase\\s*rollback\\s*$", getStartMultiLineCommentSequence());
    protected final Pattern ROLLBACK_MULTI_LINE_START_PATTERN = Pattern.compile(ROLLBACK_MULTI_LINE_START_REGEX, Pattern.CASE_INSENSITIVE);

    protected final String ROLLBACK_MULTI_LINE_END_REGEX = String.format(".*\\s*%s\\s*$", getEndMultiLineCommentSequence());
    protected final Pattern ROLLBACK_MULTI_LINE_END_PATTERN = Pattern.compile(ROLLBACK_MULTI_LINE_END_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String WORD_RESULT_REGEX = "^(?:expectedResult:)?(\\w+) (.*)";
    protected static final String SINGLE_QUOTE_RESULT_REGEX = "^(?:expectedResult:)?'([^']+)' (.*)";
    protected static final String DOUBLE_QUOTE_RESULT_REGEX = "^(?:expectedResult:)?\"([^\"]+)\" (.*)";

    protected final Pattern[] WORD_AND_QUOTING_PATTERNS = new Pattern[]{
            Pattern.compile(WORD_RESULT_REGEX, Pattern.CASE_INSENSITIVE),
            Pattern.compile(SINGLE_QUOTE_RESULT_REGEX, Pattern.CASE_INSENSITIVE),
            Pattern.compile(DOUBLE_QUOTE_RESULT_REGEX, Pattern.CASE_INSENSITIVE)
    };

    protected static final String NAME_REGEX = ".*name:\\s*(\\S++).*";
    protected final Pattern NAME_PATTERN = Pattern.compile(NAME_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String VALUE_REGEX = ".*value:\\s*(\\S+).*";
    protected final Pattern VALUE_PATTERN = Pattern.compile(VALUE_REGEX, Pattern.CASE_INSENSITIVE);

    protected static final String GLOBAL_REGEX = ".*global:(\\S+).*";
    protected final Pattern GLOBAL_PATTERN = Pattern.compile(GLOBAL_REGEX, Pattern.CASE_INSENSITIVE);

    protected abstract String getSingleLineCommentOneCharacter();

    protected abstract String getSingleLineCommentSequence();

    protected abstract String getStartMultiLineCommentSequence();

    protected abstract String getEndMultiLineCommentSequence();

    protected abstract boolean supportsExtension(String changelogFile);

    protected abstract void handlePreconditionCase(ChangeLogParameters changeLogParameters, ChangeSet changeSet, Matcher preconditionMatcher) throws ChangeLogParseException;

    protected abstract void handlePreconditionsCase(ChangeSet changeSet, int count, Matcher preconditionsMatcher) throws ChangeLogParseException;

    protected abstract AbstractSQLChange getChange();

    protected abstract String getDocumentationLink();

    protected abstract String getSequenceName();

    protected abstract void setChangeSequence(AbstractSQLChange change, String finalCurrentSequence);

    protected abstract boolean isNotEndDelimiter(AbstractSQLChange change);

    protected abstract void setChangeSequence(ChangeLogParameters changeLogParameters, StringBuilder currentSequence, ChangeSet changeSet, AbstractSQLChange change);


    @Override
    public boolean supports(String changeLogFile, ResourceAccessor resourceAccessor) {
        BufferedReader reader = null;
        try {
            if (supportsExtension(changeLogFile)) {
                InputStream fileStream = openChangeLogFile(changeLogFile, resourceAccessor);
                if (fileStream == null) {
                    return false;
                }
                reader = new BufferedReader(StreamUtil.readStreamWithReader(fileStream, null));

                String firstLine = reader.readLine();

                while (firstLine != null && firstLine.trim().isEmpty() && reader.ready()) {
                    firstLine = reader.readLine();
                }

                //
                // Handle empty files with a WARNING message
                //
                if (StringUtil.isEmpty(firstLine)) {
                    Scope.getCurrentScope().getLog(getClass()).warning(String.format("Skipping empty file '%s'", changeLogFile));
                    return false;
                }
                return FIRST_LINE_PATTERN.matcher(firstLine).matches();
            } else {
                return false;
            }
        } catch (IOException e) {
            Scope.getCurrentScope().getLog(getClass()).fine("Exception reading " + changeLogFile, e);
            return false;
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    Scope.getCurrentScope().getLog(getClass()).fine("Exception closing " + changeLogFile, e);
                }
            }
        }
    }

    @Override
    public int getPriority() {
        return PRIORITY_DEFAULT + 5;
    }

    @Override
    public DatabaseChangeLog parse(String physicalChangeLogLocation, ChangeLogParameters changeLogParameters, ResourceAccessor resourceAccessor) throws ChangeLogParseException {

        DatabaseChangeLog changeLog = new DatabaseChangeLog();
        changeLog.setChangeLogParameters(changeLogParameters);

        changeLog.setPhysicalFilePath(physicalChangeLogLocation);

        try (BufferedReader reader = new BufferedReader(StreamUtil.readStreamWithReader(openChangeLogFile(physicalChangeLogLocation, resourceAccessor), null))) {
            StringBuilder currentSequence = new StringBuilder();
            StringBuilder currentRollbackSequence = new StringBuilder();

            ChangeSet changeSet = null;
            AbstractSQLChange change = null;

            Matcher rollbackSplitStatementsPatternMatcher = null;
            boolean rollbackSplitStatements = true;
            String rollbackEndDelimiter = null;

            int count = 0;
            String line;
            while ((line = reader.readLine()) != null) {
                count++;
                Matcher commentMatcher = COMMENT_PATTERN.matcher(line);
                Matcher propertyPatternMatcher = PROPERTY_PATTERN.matcher(line);
                Matcher altPropertyPatternMatcher = ALT_PROPERTY_ONE_CHARACTER_PATTERN.matcher(line);
                if (propertyPatternMatcher.matches()) {
                    handleProperty(changeLogParameters, changeLog, line);
                    continue;
                } else if (altPropertyPatternMatcher.matches()) {
                    String message = String.format(EXCEPTION_MESSAGE, physicalChangeLogLocation, count, getSequenceName(), "--property name= value=", getDocumentationLink());
                    throw new ChangeLogParseException("\n" + message);
                }
                Matcher changeLogPatterMatcher = FIRST_LINE_PATTERN.matcher(line);

                setLogicalFilePath(changeLog, line, changeLogPatterMatcher);

                Matcher ignoreLinesMatcher = IGNORE_LINES_PATTERN.matcher(line);
                Matcher altIgnoreMatcher = ALT_IGNORE_PATTERN.matcher(line);
                Matcher altIgnoreLinesOneDashMatcher = ALT_IGNORE_LINES_ONE_CHARACTER_PATTERN.matcher(line);
                if (ignoreLinesMatcher.matches()) {
                    if ("start".equals(ignoreLinesMatcher.group(1))) {
                        while ((line = reader.readLine()) != null) {
                            altIgnoreLinesOneDashMatcher = ALT_IGNORE_LINES_ONE_CHARACTER_PATTERN.matcher(line);
                            count++;
                            ignoreLinesMatcher = IGNORE_LINES_PATTERN.matcher(line);
                            if (ignoreLinesMatcher.matches()) {
                                if ("end".equals(ignoreLinesMatcher.group(1))) {
                                    break;
                                }
                            } else if (altIgnoreLinesOneDashMatcher.matches()) {
                                String message =
                                        String.format(EXCEPTION_MESSAGE, physicalChangeLogLocation, count, getSequenceName(), "--ignoreLines:end", getDocumentationLink());
                                throw new ChangeLogParseException("\n" + message);
                            }
                        }
                        continue;
                    } else {
                        try {
                            long ignoreCount = Long.parseLong(ignoreLinesMatcher.group(1));
                            while (ignoreCount > 0 && reader.readLine() != null) {
                                ignoreCount--;
                                count++;
                            }
                            continue;
                        } catch (NumberFormatException | NullPointerException nfe) {
                            throw new ChangeLogParseException("Unknown ignoreLines syntax");
                        }
                    }
                } else if (altIgnoreLinesOneDashMatcher.matches() || altIgnoreMatcher.matches()) {
                    String message =
                            String.format(EXCEPTION_MESSAGE, physicalChangeLogLocation, count, getSequenceName(), "--ignoreLines:", getDocumentationLink());
                    throw new ChangeLogParseException("\n" + message);
                }

                Matcher changeSetPatternMatcher = CHANGE_SET_PATTERN.matcher(line);
                if (changeSetPatternMatcher.matches()) {
                    String finalCurrentSequence = changeLogParameters.expandExpressions(StringUtil.trimToNull(currentSequence.toString()), changeLog);
                    if (changeSet != null) {
                        if (finalCurrentSequence == null) {
                            throw new ChangeLogParseException(String.format("No %s for changeset %s", getSequenceName(), changeSet.toString(false)));
                        }

                        setChangeSequence(change, finalCurrentSequence);

                        handleRollbackSequence(physicalChangeLogLocation, changeLogParameters, changeLog, currentRollbackSequence, changeSet, rollbackSplitStatementsPatternMatcher, rollbackSplitStatements, rollbackEndDelimiter);
                    }

                    Matcher stripCommentsPatternMatcher = STRIP_COMMENTS_PATTERN.matcher(line);
                    Matcher splitStatementsPatternMatcher = SPLIT_STATEMENTS_PATTERN.matcher(line);
                    Matcher runWithMatcher = RUN_WITH_PATTERN.matcher(line);
                    Matcher runWithSpoolFileMatcher = RUN_WITH_SPOOL_FILE_PATTERN.matcher(line);
                    rollbackSplitStatementsPatternMatcher = ROLLBACK_SPLIT_STATEMENTS_PATTERN.matcher(line);
                    Matcher endDelimiterPatternMatcher = END_DELIMITER_PATTERN.matcher(line);
                    Matcher rollbackEndDelimiterPatternMatcher = ROLLBACK_END_DELIMITER_PATTERN.matcher(line);

                    Matcher logicalFilePathMatcher = LOGICAL_FILE_PATH_PATTERN.matcher(line);
                    Matcher runOnChangePatternMatcher = RUN_ON_CHANGE_PATTERN.matcher(line);
                    Matcher runAlwaysPatternMatcher = RUN_ALWAYS_PATTERN.matcher(line);
                    Matcher contextPatternMatcher = CONTEXT_PATTERN.matcher(line);
                    Matcher contextFilterPatternMatcher = CONTEXT_FILTER_PATTERN.matcher(line);
                    Matcher labelsPatternMatcher = LABELS_PATTERN.matcher(line);
                    Matcher runInTransactionPatternMatcher = RUN_IN_TRANSACTION_PATTERN.matcher(line);
                    Matcher dbmsPatternMatcher = DBMS_PATTERN.matcher(line);
                    Matcher ignorePatternMatcher = IGNORE_PATTERN.matcher(line);
                    Matcher failOnErrorPatternMatcher = FAIL_ON_ERROR_PATTERN.matcher(line);

                    boolean stripComments = parseBoolean(stripCommentsPatternMatcher, changeSet, true);
                    boolean splitStatements = parseBoolean(splitStatementsPatternMatcher, changeSet, true);
                    rollbackSplitStatements = parseBoolean(rollbackSplitStatementsPatternMatcher, changeSet, true);
                    boolean runOnChange = parseBoolean(runOnChangePatternMatcher, changeSet, false);
                    boolean runAlways = parseBoolean(runAlwaysPatternMatcher, changeSet, false);
                    boolean runInTransaction = parseBoolean(runInTransactionPatternMatcher, changeSet, true);
                    boolean failOnError = parseBoolean(failOnErrorPatternMatcher, changeSet, true);

                    String runWith = parseString(runWithMatcher);
                    if (runWith != null) {
                        runWith = changeLogParameters.expandExpressions(runWith, changeLog);
                    }
                    String runWithSpoolFile = parseString(runWithSpoolFileMatcher);
                    if (runWithSpoolFile != null) {
                        runWithSpoolFile = changeLogParameters.expandExpressions(runWithSpoolFile, changeLog);
                    }
                    String endDelimiter = parseString(endDelimiterPatternMatcher);
                    rollbackEndDelimiter = parseString(rollbackEndDelimiterPatternMatcher);
                    String context = parseString(contextFilterPatternMatcher);
                    if (context == null || context.isEmpty()) {
                        context = parseString(contextPatternMatcher);
                    }

                    if (context != null) {
                        context = changeLogParameters.expandExpressions(StringUtil.stripEnclosingQuotes(context), changeLog);
                    }
                    String labels = parseString(labelsPatternMatcher);
                    if (labels != null) {
                        labels = changeLogParameters.expandExpressions(StringUtil.stripEnclosingQuotes(labels), changeLog);
                    }
                    String logicalFilePath = parseString(logicalFilePathMatcher);
                    if ((logicalFilePath == null) || logicalFilePath.isEmpty()) {
                        logicalFilePath = changeLog.getLogicalFilePath();
                    }
                    if (logicalFilePath != null) {
                        logicalFilePath = changeLogParameters.expandExpressions(logicalFilePath, changeLog);
                    }
                    String dbms = parseString(dbmsPatternMatcher);
                    if (dbms != null) {
                        dbms = changeLogParameters.expandExpressions(dbms, changeLog);
                    }

                    String ignore = parseString(ignorePatternMatcher);
                    if (ignore != null) {
                        ignore = changeLogParameters.expandExpressions(ignore, changeLog);
                    }

                    //
                    // Make sure that this line matches the --changeset : with no spaces before ID
                    //
                    String idGroup = changeSetPatternMatcher.group(2);
                    String authorGroup = changeSetPatternMatcher.group(1);

                    //
                    // Use Pattern.Quote to escape the meta-characters
                    // <([{\^-=$!|]})?*+.>
                    //
                    Pattern changeSetAuthorIdPattern =
                            Pattern.compile(String.format("\\s*%s[\\s]*changeset\\s+", getSingleLineCommentSequence()) + Pattern.quote(authorGroup + ":" + idGroup) + ".*$", Pattern.CASE_INSENSITIVE);
                    Matcher changeSetAuthorIdPatternMatcher = changeSetAuthorIdPattern.matcher(line);
                    if (!changeSetAuthorIdPatternMatcher.matches()) {
                        String message =
                                String.format(EXCEPTION_MESSAGE, physicalChangeLogLocation, count, getSequenceName(), "--changeset :", getDocumentationLink());
                        throw new ChangeLogParseException("\n" + message);
                    }

                    String changeSetId = changeLogParameters.expandExpressions(StringUtil.stripEnclosingQuotes(idGroup), changeLog);
                    String changeSetAuthor = changeLogParameters.expandExpressions(StringUtil.stripEnclosingQuotes(authorGroup), changeLog);

                    changeSet = configureChangeSet(changeLog, runOnChange, runAlways, runInTransaction, failOnError, runWith, runWithSpoolFile, context, labels, logicalFilePath, dbms, ignore, changeSetId, changeSetAuthor);
                    changeLog.addChangeSet(changeSet);

                    change = getChange();
                    setChangeSequence(change, finalCurrentSequence);

                    if (splitStatementsPatternMatcher.matches()) {
                        change.setSplitStatements(splitStatements);
                    }
                    change.setStripComments(stripComments);
                    change.setEndDelimiter(endDelimiter);
                    changeSet.addChange(change);

                    currentSequence.setLength(0);
                    currentRollbackSequence.setLength(0);
                } else {
                    Matcher altChangeSetOneDashPatternMatcher = ALT_CHANGE_SET_ONE_CHARACTER_PATTERN.matcher(line);
                    Matcher altChangeSetNoOtherInfoPatternMatcher = ALT_CHANGE_SET_NO_OTHER_INFO_PATTERN.matcher(line);
                    if (altChangeSetOneDashPatternMatcher.matches() || altChangeSetNoOtherInfoPatternMatcher.matches()) {
                        String message = String.format(EXCEPTION_MESSAGE, physicalChangeLogLocation, count, getSequenceName(), "--changeset :", getDocumentationLink());
                        throw new ChangeLogParseException("\n" + message);
                    }
                    if (changeSet != null) {
                        configureChangeSet(physicalChangeLogLocation, changeLogParameters, reader, currentSequence, currentRollbackSequence, changeSet, count, line, commentMatcher);
                    } else {
                        if (commentMatcher.matches()) {
                            String message =
                                    String.format("Unexpected formatting at line %d. Formatted %s changelogs do not allow comment lines outside of changesets. Learn all the options at %s", count, getSequenceName(), getDocumentationLink());
                            throw new ChangeLogParseException("\n" + message);
                        }
                    }
                }
            }

            if (changeSet != null) {
                setChangeSequence(changeLogParameters, currentSequence, changeSet, change);

                if (isNotEndDelimiter(change)) {
                    change.setEndDelimiter("\n/$");
                }

                handleRollbackSequence(physicalChangeLogLocation, changeLogParameters, changeLog, currentRollbackSequence, changeSet, rollbackSplitStatementsPatternMatcher, rollbackSplitStatements, rollbackEndDelimiter);
            }

        } catch (IOException e) {
            throw new ChangeLogParseException(e);
        }

        return changeLog;
    }

    protected void configureChangeSet(String physicalChangeLogLocation, ChangeLogParameters changeLogParameters, BufferedReader reader, StringBuilder currentSequence, StringBuilder currentRollbackSequence, ChangeSet changeSet, int count, String line, Matcher commentMatcher) throws ChangeLogParseException, IOException {
        Matcher altCommentOneDashMatcher = ALT_COMMENT_ONE_CHARACTER_PATTERN.matcher(line);
        Matcher altCommentPluralMatcher = ALT_COMMENT_PLURAL_PATTERN.matcher(line);
        Matcher rollbackMatcher = ROLLBACK_PATTERN.matcher(line);
        Matcher altRollbackMatcher = ALT_ROLLBACK_ONE_CHARACTER_PATTERN.matcher(line);
        Matcher preconditionsMatcher = PRECONDITIONS_PATTERN.matcher(line);
        Matcher altPreconditionsOneDashMatcher = ALT_PRECONDITIONS_ONE_CHARACTER_PATTERN.matcher(line);
        Matcher preconditionMatcher = PRECONDITION_PATTERN.matcher(line);
        Matcher altPreconditionOneDashMatcher = ALT_PRECONDITION_ONE_CHARACTER_PATTERN.matcher(line);
        Matcher validCheckSumMatcher = VALID_CHECK_SUM_PATTERN.matcher(line);
        Matcher altValidCheckSumOneDashMatcher = ALT_VALID_CHECK_SUM_ONE_CHARACTER_PATTERN.matcher(line);
        Matcher rollbackMultiLineStartMatcher = ROLLBACK_MULTI_LINE_START_PATTERN.matcher(line);

        if (commentMatcher.matches()) {
            if (commentMatcher.groupCount() == 0) {
                String message = String.format(EXCEPTION_MESSAGE, physicalChangeLogLocation, count, getSequenceName(), "--comment ", getDocumentationLink());
                throw new ChangeLogParseException("\n" + message);
            }
            if (commentMatcher.groupCount() == 1) {
                changeSet.setComments(commentMatcher.group(1));
            }
        } else if (altCommentOneDashMatcher.matches() || altCommentPluralMatcher.matches()) {
            String message = String.format(EXCEPTION_MESSAGE, physicalChangeLogLocation, count, getSequenceName(), "--comment ", getDocumentationLink());
            throw new ChangeLogParseException("\n" + message);
        } else if (validCheckSumMatcher.matches()) {
            if (validCheckSumMatcher.groupCount() == 0) {
                String message = String.format(EXCEPTION_MESSAGE, physicalChangeLogLocation, count, getSequenceName(), String.format("--rollback ", getSequenceName()), getDocumentationLink());
                throw new ChangeLogParseException("\n" + message);
            } else if (validCheckSumMatcher.groupCount() == 1) {
                changeSet.addValidCheckSum(validCheckSumMatcher.group(1));
            }
        } else if (altValidCheckSumOneDashMatcher.matches()) {
            String message = String.format(EXCEPTION_MESSAGE, physicalChangeLogLocation, count, getSequenceName(), "--validChecksum ", getDocumentationLink());
            throw new ChangeLogParseException("\n" + message);
        } else if (rollbackMatcher.matches()) {
            if (rollbackMatcher.groupCount() == 0) {
                String message = String.format(EXCEPTION_MESSAGE, physicalChangeLogLocation, count, getSequenceName(), String.format("--rollback ", getSequenceName()), getDocumentationLink());
                throw new ChangeLogParseException("\n" + message);
            }
            currentRollbackSequence.append(rollbackMatcher.group(1)).append(System.lineSeparator());
        } else if (altRollbackMatcher.matches()) {
            String message = String.format(EXCEPTION_MESSAGE, physicalChangeLogLocation, count, getSequenceName(), String.format("--rollback ", getSequenceName()), getDocumentationLink());
            throw new ChangeLogParseException("\n" + message);
        } else if (rollbackMultiLineStartMatcher.matches()) {
            if (rollbackMultiLineStartMatcher.groupCount() == 0) {
                currentRollbackSequence.append(extractMultiLineRollBack(reader));
            }
        } else if (preconditionsMatcher.matches()) {
            handlePreconditionsCase(changeSet, count, preconditionsMatcher);
        } else if (altPreconditionsOneDashMatcher.matches()) {
            String message = String.format(EXCEPTION_MESSAGE, physicalChangeLogLocation, count, getSequenceName(), "--preconditions ||", getDocumentationLink());
            throw new ChangeLogParseException("\n" + message);
        } else if (preconditionMatcher.matches()) {
            handlePreconditionCase(changeLogParameters, changeSet, preconditionMatcher);
        } else if (altPreconditionOneDashMatcher.matches()) {
            String message =
                    String.format(EXCEPTION_MESSAGE, physicalChangeLogLocation, count, getSequenceName(), "--precondition-sql-check", getDocumentationLink());
            throw new ChangeLogParseException("\n" + message);
        } else {
            currentSequence.append(line).append(System.lineSeparator());
        }
    }

    protected ChangeSet configureChangeSet(DatabaseChangeLog changeLog, boolean runOnChange, boolean runAlways,
                                           boolean runInTransaction, boolean failOnError, String runWith,
                                           String runWithSpoolFile, String context, String labels, String logicalFilePath,
                                           String dbms, String ignore, String changeSetId, String changeSetAuthor) {
        ChangeSetService service = ChangeSetServiceFactory.getInstance().createChangeSetService();
        ChangeSet changeSet =
           service.createChangeSet(changeSetId, changeSetAuthor, runAlways, runOnChange,
                                   DatabaseChangeLog.normalizePath(logicalFilePath),
                                   context, dbms, runWith, runWithSpoolFile,
                                   runInTransaction,
                                   changeLog.getObjectQuotingStrategy(), changeLog);
        changeSet.setLabels(new Labels(labels));
        changeSet.setIgnore(Boolean.parseBoolean(ignore));
        changeSet.setFailOnError(failOnError);
        return changeSet;
    }

    protected void setLogicalFilePath(DatabaseChangeLog changeLog, String line, Matcher changeLogPatterMatcher) {
        if (changeLogPatterMatcher.matches()) {
            Matcher logicalFilePathMatcher = LOGICAL_FILE_PATH_PATTERN.matcher(line);
            changeLog.setLogicalFilePath(parseString(logicalFilePathMatcher));
        }
    }

    protected String parseString(Matcher matcher) {
        String endDelimiter = null;
        if (matcher.matches()) {
            endDelimiter = matcher.group(1);
        }
        return endDelimiter;
    }

    protected InputStream openChangeLogFile(String physicalChangeLogLocation, ResourceAccessor resourceAccessor) throws IOException {
        return resourceAccessor.getExisting(physicalChangeLogLocation).openInputStream();
    }

    private void handleRollbackSequence(String physicalChangeLogLocation, ChangeLogParameters changeLogParameters, DatabaseChangeLog changeLog, StringBuilder currentRollbackSequence, ChangeSet changeSet, Matcher rollbackSplitStatementsPatternMatcher, boolean rollbackSplitStatements, String rollbackEndDelimiter) throws ChangeLogParseException {
        String currentRollbackSequenceAsString = currentRollbackSequence.toString();
        if (StringUtil.trimToNull(currentRollbackSequenceAsString) != null) {
            if (currentRollbackSequenceAsString.trim().toLowerCase().matches("^not required.*") || currentRollbackSequence.toString().trim().toLowerCase().matches("^empty.*")) {
                changeSet.addRollbackChange(new EmptyChange());
            } else if (currentRollbackSequenceAsString.trim().toLowerCase().contains("changesetid")) {
                configureRollbackChangeSet(physicalChangeLogLocation, changeLog, changeSet, currentRollbackSequenceAsString);
            } else {
                configureRollbackChange(changeLogParameters, currentRollbackSequence, changeSet, rollbackSplitStatementsPatternMatcher, rollbackSplitStatements, rollbackEndDelimiter);
            }
        }
    }

    private void configureRollbackChange(ChangeLogParameters changeLogParameters, StringBuilder currentRollbackSequence, ChangeSet changeSet, Matcher rollbackSplitStatementsPatternMatcher, boolean rollbackSplitStatements, String rollbackEndDelimiter) {
        AbstractSQLChange rollbackChange = getChange();
        setChangeSequence(rollbackChange, changeLogParameters.expandExpressions(currentRollbackSequence.toString(), changeSet.getChangeLog()));
        if (rollbackSplitStatementsPatternMatcher.matches()) {
            rollbackChange.setSplitStatements(rollbackSplitStatements);
        }
        if (rollbackEndDelimiter != null) {
            rollbackChange.setEndDelimiter(rollbackEndDelimiter);
        }
        changeSet.addRollbackChange(rollbackChange);
    }

    private void configureRollbackChangeSet(String physicalChangeLogLocation, DatabaseChangeLog changeLog, ChangeSet changeSet, String currentRollBackSequenceAsString) throws ChangeLogParseException {
        String rollbackString = currentRollBackSequenceAsString.replace("\n", "").replace("\r", "");
        Matcher authorMatcher = ROLLBACK_CHANGE_SET_AUTHOR_PATTERN.matcher(rollbackString);
        Matcher idMatcher = ROLLBACK_CHANGE_SET_ID_PATTERN.matcher(rollbackString);
        Matcher pathMatcher = ROLLBACK_CHANGE_SET_PATH_PATTERN.matcher(rollbackString);

        String changeSetAuthor = StringUtil.trimToNull(parseString(authorMatcher));
        String changeSetId = StringUtil.trimToNull(parseString(idMatcher));
        String changeSetPath = StringUtil.trimToNull(parseString(pathMatcher));

        if (changeSetId == null) {
            throw new ChangeLogParseException("'changesetId' not set in rollback block '" + rollbackString + "'");
        }

        if (changeSetAuthor == null) {
            throw new ChangeLogParseException("'changesetAuthor' not set in rollback block '" + rollbackString + "'");
        }

        if (changeSetPath == null) {
            changeSetPath = physicalChangeLogLocation;
        }

        ChangeSet rollbackChangeSet = changeLog.getChangeSet(changeSetPath, changeSetAuthor, changeSetId);
        DatabaseChangeLog parent = changeLog;
        while ((rollbackChangeSet == null) && (parent != null)) {
            parent = parent.getParentChangeLog();
            if (parent != null) {
                rollbackChangeSet = parent.getChangeSet(changeSetPath, changeSetAuthor, changeSetId);
            }
        }

        if (rollbackChangeSet == null) {
            throw new ChangeLogParseException("Change set " + new ChangeSet(changeSetId, changeSetAuthor, false, false, changeSetPath, null, null, null).toString(false) + " does not exist");
        }
        for (Change rollbackChange : rollbackChangeSet.getChanges()) {
            changeSet.addRollbackChange(rollbackChange);
        }
    }

    private void handleProperty(ChangeLogParameters changeLogParameters, DatabaseChangeLog changeLog, String line) {
        Matcher namePatternMatcher = NAME_PATTERN.matcher(line);
        Matcher valuePatternMatcher = VALUE_PATTERN.matcher(line);
        Matcher contextPatternMatcher = CONTEXT_PATTERN.matcher(line);
        Matcher contextFilterPatternMatcher = CONTEXT_FILTER_PATTERN.matcher(line);
        Matcher labelsPatternMatcher = LABELS_PATTERN.matcher(line);
        Matcher dbmsPatternMatcher = DBMS_PATTERN.matcher(line);
        Matcher globalPatternMatcher = GLOBAL_PATTERN.matcher(line);

        String name = parseString(namePatternMatcher);
        if (name != null) {
            name = changeLogParameters.expandExpressions(name, changeLog);
        }

        String value = parseString(valuePatternMatcher);
        if (value != null) {
            value = changeLogParameters.expandExpressions(value, changeLog);
        }

        String context = parseString(contextFilterPatternMatcher);
        if (context == null || context.isEmpty()) {
            context = parseString(contextPatternMatcher);
        }
        if (context != null) {
            context = changeLogParameters.expandExpressions(StringUtil.stripEnclosingQuotes(context), changeLog);
        }

        String labels = parseString(labelsPatternMatcher);
        if (labels != null) {
            labels = changeLogParameters.expandExpressions(StringUtil.stripEnclosingQuotes(labels), changeLog);
        }

        String dbms = parseString(dbmsPatternMatcher);
        if (dbms != null) {
            dbms = changeLogParameters.expandExpressions(dbms.trim(), changeLog);
        }
        // behave like liquibase < 3.4 and set global == true (see DatabaseChangeLog.java)
        boolean global = parseBoolean(globalPatternMatcher, true);

        changeLogParameters.set(name, value, context, labels, dbms, global, changeLog);
    }

    private StringBuilder extractMultiLineRollBack(BufferedReader reader) throws IOException, ChangeLogParseException {
        StringBuilder multiLineRollback = new StringBuilder();

        String line;
        if (reader != null) {
            while ((line = reader.readLine()) != null) {
                if (ROLLBACK_MULTI_LINE_END_PATTERN.matcher(line).matches()) {
                    String[] lastLineSplit = line.split(String.format("%s\\s*$", getEndMultiLineCommentSequence()));
                    if (lastLineSplit.length > 0 && !StringUtil.isWhitespace(lastLineSplit[0])) {
                        multiLineRollback.append(lastLineSplit[0]);
                    }
                    return multiLineRollback;
                }
                multiLineRollback.append(line);
            }
            throw new ChangeLogParseException("Liquibase rollback comment is not closed.");
        }
        return multiLineRollback;
    }

    private boolean parseBoolean(Matcher matcher, boolean defaultValue) {
        boolean value = defaultValue;
        if (matcher.matches()) {
            value = Boolean.parseBoolean(matcher.group(1));
        }
        return value;
    }

    private boolean parseBoolean(Matcher matcher, ChangeSet changeSet, boolean defaultValue) throws ChangeLogParseException {
        boolean stripComments = defaultValue;
        if (matcher.matches()) {
            try {
                stripComments = Boolean.parseBoolean(matcher.group(1));
            } catch (Exception e) {
                throw new ChangeLogParseException("Cannot parse " + changeSet + " " + matcher.toString().replaceAll("\\.*", "") + " as a boolean");
            }
        }
        return stripComments;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy