/* 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 java.util.Objects;
import java.util.StringJoiner;

import org.flowable.common.engine.api.management.TablePage;
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.flowable.common.engine.impl.persistence.entity.TableDataManager;
import org.flowable.common.engine.impl.persistence.entity.TablePageQueryImpl;
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");
        TableDataManager tableDataManager = engineConfiguration.getTableDataManager();
        Map<String, Long> tableCounts = engineConfiguration.getCommandExecutor().execute(commandContext -> tableDataManager.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) : \n");

                    TablePage tableData = engineConfiguration.getCommandExecutor()
                        .execute(commandContext -> tableDataManager.getTablePage(new TablePageQueryImpl().tableName(tableName), 0, count.intValue()));
                    if (tableData != null && tableData.getRows() != null) {

                        StringJoiner stringJoiner = new StringJoiner(", ", "{", "}");
                        for (Map<String, Object> row : tableData.getRows()) {
                            outputMessage.append("    ");
                            for (String key: row.keySet()) {
                                Object value = row.get(key);
                                String stringValue = null;
                                if (value instanceof byte[]) {
                                    stringValue = new String((byte[]) value);
                                } else {
                                    stringValue = Objects.toString(value, null);
                                }

                                stringJoiner.add(key + " = " + stringValue);
                            }

                            outputMessage.append(stringJoiner.toString());
                            outputMessage.append("\n");
                        }
                    }
                }
            }
        }
        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");
        }
    }

}

@@ -600,16 +600,72 @@
 .test;%0A%0A
+import java.io.IOException;%0Aimport java.io.InputStream;%0A
 import j
@@ -816,16 +816,74 @@
 oiner;%0A%0A
+import org.apache.commons.lang3.exception.ExceptionUtils;%0A
 import o
@@ -1350,16 +1350,68 @@
 ryImpl;%0A
+import org.flowable.common.engine.impl.util.IoUtil;%0A
 import o
@@ -3759,147 +3759,151 @@
     
-StringJoiner stringJoiner = new StringJoiner(%22, %22, %22%7B%22, %22%7D%22);%0A                        for (Map%3CString, Object%3E row : tableData.getRows()) %7B
+for (Map%3CString, Object%3E row : tableData.getRows()) %7B%0A                            StringJoiner stringJoiner = new StringJoiner(%22, %22, %22%7B%22, %22%7D%22);
 %0A   
@@ -4197,24 +4197,24 @@
 f byte%5B%5D) %7B%0A
-
             
@@ -4275,24 +4275,581 @@
 %5B%5D) value);%0A
+                                %7D else if (value instanceof InputStream) %7B%0A                                    try (InputStream stream = (InputStream) value) %7B%0A                                        stringValue = new String(IoUtil.readInputStream(stream, %22row value for %22 + key));%0A                                    %7D catch (Exception exception) %7B%0A                                        stringValue = %22Failed to read stream: %22 + Objects.toString(value, null) + %22. Error: %22 + ExceptionUtils.getStackTrace(exception);%0A                                    %7D%0A
             

Improve DB not empty error test output