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

liquibase.util.ShowSummaryUtil Maven / Gradle / Ivy

There is a newer version: 4.29.1
Show newest version
package liquibase.util;

import liquibase.GlobalConfiguration;
import liquibase.Scope;
import liquibase.UpdateSummaryEnum;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.ChangeSetStatus;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.changelog.filter.*;
import liquibase.changelog.visitor.StatusVisitor;
import liquibase.exception.LiquibaseException;
import liquibase.logging.mdc.MdcKey;
import liquibase.logging.mdc.MdcObject;
import liquibase.logging.mdc.customobjects.UpdateSummary;

import java.io.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 *
 * Methods to show a summary of change set counts after an update
 *
 */
public class ShowSummaryUtil {

    /**
     *
     * Show a summary of the changesets which were executed
     *
     * @param   changeLog                          The changelog used in this update
     * @param   showSummary                        Flag to control whether or not we show the summary
     * @param   statusVisitor                      The StatusVisitor used to determine statuses
     * @param   outputStream                       The OutputStream to use for the summary
     * @throws  LiquibaseException                 Thrown by this method
     * @throws  IOException                        Thrown by this method
     *
     */
    public static void showUpdateSummary(DatabaseChangeLog changeLog, UpdateSummaryEnum showSummary, StatusVisitor statusVisitor, OutputStream outputStream)
            throws LiquibaseException, IOException {
        //
        // Check the global flag to turn the summary off
        //
        if (showSummary == null || showSummary == UpdateSummaryEnum.OFF) {
            return;
        }

        //
        // Obtain two lists:  the list of filtered change sets that
        // The StatusVisitor discovered, and also any change sets which
        // were skipped during parsing, i.e. they had mismatched DBMS values
        //
        List denied = statusVisitor.getChangeSetsToSkip();
        List skippedChangeSets = changeLog.getSkippedChangeSets();

        //
        // Filter the skipped list to remove changes which were:
        // Previously run
        // After the tag
        // After the count value
        //
        List filterDenied =
                denied.stream()
                        .filter(status -> status.getFilterResults()
                                .stream().anyMatch(result ->  result.getFilter() != ShouldRunChangeSetFilter.class))
                        .collect(Collectors.toList());

        //
        // Only show the summary
        //
        UpdateSummary updateSummaryMdc = showSummary(changeLog, statusVisitor, skippedChangeSets, filterDenied, outputStream);
        updateSummaryMdc.setValue(showSummary.toString());
        boolean shouldPrintDetailTable = showSummary != UpdateSummaryEnum.SUMMARY && (!skippedChangeSets.isEmpty() || !denied.isEmpty());

        //
        // Show the details too
        //
        SortedMap skippedMdc = showDetailTable(skippedChangeSets, filterDenied, outputStream, shouldPrintDetailTable);
        updateSummaryMdc.setSkipped(skippedMdc);
        try(MdcObject updateSummaryMdcObject = Scope.getCurrentScope().addMdcValue(MdcKey.UPDATE_SUMMARY, updateSummaryMdc)) {
            Scope.getCurrentScope().getLog(ShowSummaryUtil.class).info("Update summary generated");
        }
    }

    //
    // Show the details
    //
    private static SortedMap showDetailTable(List skippedChangeSets, List filterDenied, OutputStream outputStream, boolean shouldPrintDetailTable)
            throws IOException, LiquibaseException {
        String totalSkippedMdcKey = "totalSkipped";
        //
        // Nothing to do
        //
        if (filterDenied.isEmpty() && skippedChangeSets.isEmpty()) {
            return new TreeMap<>(Collections.singletonMap(totalSkippedMdcKey, 0));
        }
        List columnHeaders = new ArrayList<>();
        columnHeaders.add("Changeset Info");
        columnHeaders.add("Reason Skipped");
        List> table = new ArrayList<>();
        table.add(columnHeaders);
        SortedMap mdcSkipCounts = new TreeMap<>();
        mdcSkipCounts.put(totalSkippedMdcKey, skippedChangeSets.size() + filterDenied.size());

        List finalList = createFinalStatusList(skippedChangeSets, filterDenied, mdcSkipCounts);

        finalList.sort(new Comparator() {
            @Override
            public int compare(ChangeSetStatus o1, ChangeSetStatus o2) {
                ChangeSet c1 = o1.getChangeSet();
                ChangeSet c2 = o2.getChangeSet();
                int order1 = determineOrderInChangelog(c1);
                int order2 = determineOrderInChangelog(c2);
                if (order1 == -1 || order2 == -1) {
                    return -1;
                }
                return Integer.compare(order1, order2);
            }
        });

        //
        // Filtered because of labels or context
        //
        for (ChangeSetStatus st : finalList) {
            AtomicBoolean flag = new AtomicBoolean(true);
            StringBuilder builder = new StringBuilder();
            st.getFilterResults().forEach(consumer -> {
                if (consumer.getFilter() != null) {
                    String displayName = consumer.getMdcName();
                    mdcSkipCounts.merge(displayName, 1, Integer::sum);
                }
                String skippedMessage = String.format("   '%s' : %s", st.getChangeSet().toString(), consumer.getMessage());
                Scope.getCurrentScope().getLog(ShowSummaryUtil.class).info(skippedMessage);
                if (! flag.get()) {
                    builder.append(System.lineSeparator());
                }
                builder.append(consumer.getMessage());
                flag.set(false);
            });
            List outputRow = new ArrayList<>();
            outputRow.add(st.getChangeSet().toString());
            outputRow.add(builder.toString());
            table.add(outputRow);
        }

        if (shouldPrintDetailTable) {
            List widths = new ArrayList<>();
            widths.add(60);
            widths.add(40);

            Writer writer = createOutputWriter(outputStream);
            TableOutput.formatOutput(table, widths, true, writer);
        }
        return mdcSkipCounts;
    }

    //
    // Create a final list of changesets to be displayed
    //
    private static List createFinalStatusList(List skippedChangeSets, List filterDenied, SortedMap mdcSkipCounts) {
        //
        // Add skipped during changelog parsing to the final list
        //
        List finalList = new ArrayList<>(filterDenied);
        skippedChangeSets.forEach(skippedChangeSet -> {
            String dbmsList = String.format("'%s'", StringUtil.join(skippedChangeSet.getDbmsSet(), ", "));
            String mismatchMessage = String.format("mismatched DBMS value of %s", dbmsList);
            ChangeSetStatus changeSetStatus = new ChangeSetStatus(skippedChangeSet);
            ChangeSetFilterResult filterResult = new ChangeSetFilterResult(false, mismatchMessage, DbmsChangeSetFilter.class, DbmsChangeSetFilter.MDC_NAME, DbmsChangeSetFilter.DISPLAY_NAME);
            changeSetStatus.setFilterResults(Collections.singleton(filterResult));
            finalList.add(changeSetStatus);
        });
        return finalList;
    }

    //
    // Determine the change set's order in the changelog
    //
    private static int determineOrderInChangelog(ChangeSet changeSetToMatch) {
        DatabaseChangeLog changeLog = changeSetToMatch.getChangeLog();
        int order = 0;
        for (ChangeSet changeSet : changeLog.getChangeSets()) {
            if (changeSet == changeSetToMatch) {
                return order;
            }
            order++;
        }
        return -1;
    }

    //
    // Show the summary list
    //
    private static UpdateSummary showSummary(DatabaseChangeLog changeLog,
                                    StatusVisitor statusVisitor,
                                    List skippedChangeSets,
                                    List filterDenied,
                                    OutputStream outputStream) throws LiquibaseException {
        StringBuilder builder = new StringBuilder();
        builder.append(System.lineSeparator());
        int totalInChangelog = changeLog.getChangeSets().size() + skippedChangeSets.size();
        int skipped = skippedChangeSets.size();
        int filtered = filterDenied.size();
        int totalAccepted = statusVisitor.getChangeSetsToRun().size();
        int totalPreviouslyRun = totalInChangelog - filtered - skipped - totalAccepted;
        UpdateSummary updateSummaryMdc = new UpdateSummary(null, totalAccepted, totalPreviouslyRun, null, totalInChangelog);

        String message = "UPDATE SUMMARY";
        Scope.getCurrentScope().getLog(ShowSummaryUtil.class).info(message);
        builder.append(message);
        builder.append(System.lineSeparator());

        message = String.format("Run:                     %6d", totalAccepted);
        Scope.getCurrentScope().getLog(ShowSummaryUtil.class).info(message);
        builder.append(message);
        builder.append(System.lineSeparator());

        message = String.format("Previously run:          %6d", totalPreviouslyRun);
        Scope.getCurrentScope().getLog(ShowSummaryUtil.class).info(message);
        builder.append(message);
        builder.append(System.lineSeparator());

        message = String.format("Filtered out:            %6d", filtered + skipped);
        Scope.getCurrentScope().getLog(ShowSummaryUtil.class).info(message);
        builder.append(message);
        builder.append(System.lineSeparator());

        message = "-------------------------------";
        Scope.getCurrentScope().getLog(ShowSummaryUtil.class).info(message);
        builder.append(message);
        builder.append(System.lineSeparator());

        message = String.format("Total change sets:       %6d%n", totalInChangelog);
        Scope.getCurrentScope().getLog(ShowSummaryUtil.class).info(message);
        builder.append(message);
        builder.append(System.lineSeparator());

        final Map filterSummaryMap = new LinkedHashMap<>();
        List finalList = createFinalStatusList(skippedChangeSets, filterDenied, null);
        finalList.forEach(status -> {
            status.getFilterResults().forEach(result -> {
                if (! result.isAccepted()) {
                    String displayName = result.getDisplayName();
                    filterSummaryMap.merge(displayName, 1, Integer::sum);
                }
            });
        });

        if (! filterSummaryMap.isEmpty()) {
            message = String.format("%nFILTERED CHANGE SETS SUMMARY%n");
            builder.append(message);
            builder.append(System.lineSeparator());
            Scope.getCurrentScope().getLog(ShowSummaryUtil.class).info(message);
            filterSummaryMap.forEach((filterDisplayName, count) -> {
                String filterSummaryDetailMessage = String.format("%-18s       %6d",
                        filterDisplayName + ":", count);
                Scope.getCurrentScope().getLog(ShowSummaryUtil.class).info(filterSummaryDetailMessage);
                builder.append(filterSummaryDetailMessage);
                builder.append(System.lineSeparator());
            });
            builder.append(System.lineSeparator());
        }

        try {
            Writer writer = createOutputWriter(outputStream);
            writer.append(builder.toString());
            writer.flush();
        } catch (IOException ioe) {
            throw new LiquibaseException(ioe);
        }

        return updateSummaryMdc;
    }

    //
    // Create a Writer to display the summary
    //
    private static Writer createOutputWriter(OutputStream outputStream) throws IOException {
        String charsetName = GlobalConfiguration.OUTPUT_FILE_ENCODING.getCurrentValue();
        return new OutputStreamWriter(outputStream, charsetName);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy