/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.flowable.common.engine.impl.test;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;

import org.flowable.common.engine.impl.AbstractEngineConfiguration;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandConfig;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.slf4j.Logger;

/**
 * @author Filip Hrisafov
 */
public class EnsureCleanDbUtils {

    protected static final String DB_CHANGELOG_TABLE = "DATABASECHANGELOG";

    public static void assertAndEnsureCleanDb(
            String testName,
            Logger logger, AbstractEngineConfiguration engineConfiguration,
            EnsureCleanDb ensureCleanDb, boolean hasNoException, Command<Void> dropAndRecreateDbCommand) {
        Collection<String> tableNamesExcludedFromDbCleanCheck = new HashSet<>(Arrays.asList(ensureCleanDb.excludeTables()));
        // if the db should not be dropped pass null
        Command<Void> dropAndRecreateDbCommandToUse = ensureCleanDb.dropDb() ? dropAndRecreateDbCommand : null;
        assertAndEnsureCleanDb(testName, logger, engineConfiguration, tableNamesExcludedFromDbCleanCheck, hasNoException, dropAndRecreateDbCommandToUse);
    }

    public static void assertAndEnsureCleanDb(String testName, Logger logger, AbstractEngineConfiguration engineConfiguration,
            Collection<String> tableNamesExcludedFromDbCleanCheck, boolean hasNoException, Command<Void> dropAndRecreateDbCommand) {
        logger.debug("verifying that db is clean after test");
        Map<String, Long> tableCounts = engineConfiguration.getCommandExecutor()
                .execute(commandContext -> engineConfiguration.getTableDataManager().getTableCount());
        StringBuilder outputMessage = new StringBuilder();
        for (String tableName : tableCounts.keySet()) {
            String tableNameWithoutPrefix = tableName.replace(engineConfiguration.getDatabaseTablePrefix(), "");
            if (!tableNamesExcludedFromDbCleanCheck.contains(tableNameWithoutPrefix) && !tableNameWithoutPrefix.contains(DB_CHANGELOG_TABLE)) {
                Long count = tableCounts.get(tableName);
                if (count != 0L) {
                    outputMessage.append("  ").append(tableName).append(": ").append(count).append(" record(s) ");
                }
            }
        }
        if (outputMessage.length() > 0) {
            outputMessage.insert(0, "DB NOT CLEAN for " + testName + ": \n");
            logger.error("\n");
            logger.error(outputMessage.toString());

            logger.info("dropping and recreating db");

            if (dropAndRecreateDbCommand != null) {
                CommandExecutor commandExecutor = engineConfiguration.getCommandExecutor();
                CommandConfig config = new CommandConfig().transactionNotSupported();
                commandExecutor.execute(config, dropAndRecreateDbCommand);
            }

            if (hasNoException) {
                throw new AssertionError(outputMessage.toString());
            }
        } else {
            logger.info("database was clean");
        }
    }

}

@@ -702,16 +702,133 @@
 il.Map;%0A
+import java.util.Objects;%0Aimport java.util.StringJoiner;%0A%0Aimport org.flowable.common.engine.api.management.TablePage;
 %0Aimport 
@@ -1082,16 +1082,170 @@
 ecutor;%0A
+import org.flowable.common.engine.impl.persistence.entity.TableDataManager;%0Aimport org.flowable.common.engine.impl.persistence.entity.TablePageQueryImpl;%0A
 import o
@@ -2437,24 +2437,111 @@
 ter test%22);%0A
+        TableDataManager tableDataManager = engineConfiguration.getTableDataManager();%0A
         Map%3C
@@ -2608,33 +2608,16 @@
 ecutor()
-%0A                
 .execute
@@ -2639,32 +2639,9 @@
  -%3E 
-engineConfiguration.getT
+t
 able
@@ -2651,18 +2651,16 @@
 aManager
-()
 .getTabl
@@ -3245,19 +3245,1323 @@
 cord(s) 
-%22);
+: %5Cn%22);%0A%0A                    TablePage tableData = engineConfiguration.getCommandExecutor()%0A                        .execute(commandContext -%3E tableDataManager.getTablePage(new TablePageQueryImpl().tableName(tableName), 0, count.intValue()));%0A                    if (tableData != null && tableData.getRows() != null) %7B%0A%0A                        StringJoiner stringJoiner = new StringJoiner(%22, %22, %22%7B%22, %22%7D%22);%0A                        for (Map%3CString, Object%3E row : tableData.getRows()) %7B%0A                            outputMessage.append(%22    %22);%0A                            for (String key: row.keySet()) %7B%0A                                Object value = row.get(key);%0A                                String stringValue = null;%0A                                if (value instanceof byte%5B%5D) %7B%0A                                    stringValue = new String((byte%5B%5D) value);%0A                                %7D else %7B%0A                                    stringValue = Objects.toString(value, null);%0A                                %7D%0A%0A                                stringJoiner.add(key + %22 = %22 + stringValue);%0A                            %7D%0A%0A                            outputMessage.append(stringJoiner.toString());%0A                            outputMessage.append(%22%5Cn%22);%0A                        %7D%0A                    %7D
 %0A       

Test output: display table row content on table count not 0 (i.e. table not clean error msg)