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

dev.dsf.fhir.dao.command.BatchCommandList Maven / Gradle / Ivy

package dev.dsf.fhir.dao.command;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Consumer;

import javax.sql.DataSource;

import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.Bundle.BundleType;
import org.hl7.fhir.r4.model.IdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import dev.dsf.fhir.event.EventHandler;
import dev.dsf.fhir.help.ExceptionHandler;
import dev.dsf.fhir.validation.SnapshotGenerator;
import jakarta.ws.rs.WebApplicationException;

public class BatchCommandList extends AbstractCommandList implements CommandList
{
	private static final Logger logger = LoggerFactory.getLogger(BatchCommandList.class);

	private final ValidationHelper validationHelper;
	private final SnapshotGenerator snapshotGenerator;
	private final EventHandler eventHandler;

	public BatchCommandList(DataSource dataSource, ExceptionHandler exceptionHandler, List commands,
			ValidationHelper validationHelper, SnapshotGenerator snapshotGenerator, EventHandler eventHandler)
	{
		super(dataSource, exceptionHandler, commands);

		this.validationHelper = validationHelper;
		this.snapshotGenerator = snapshotGenerator;
		this.eventHandler = eventHandler;
	}

	@Override
	public Bundle execute() throws WebApplicationException
	{
		try (Connection connection = dataSource.getConnection())
		{
			boolean initialReadOnly = connection.isReadOnly();
			boolean initialAutoCommit = connection.getAutoCommit();
			int initialTransactionIsolationLevel = connection.getTransactionIsolation();
			logger.debug(
					"Running batch with DB connection setting: read-only {}, auto-commit {}, transaction-isolation-level {}",
					initialReadOnly, initialAutoCommit,
					getTransactionIsolationLevelString(initialTransactionIsolationLevel));

			Map caughtExceptions = new HashMap<>((int) (commands.size() / 0.75) + 1);
			Map idTranslationTable = new HashMap<>();

			if (hasModifyingCommands)
			{
				logger.debug(
						"Elevating DB connection setting to: read-only {}, auto-commit {}, transaction-isolation-level {}",
						false, false, getTransactionIsolationLevelString(Connection.TRANSACTION_REPEATABLE_READ));

				connection.setReadOnly(false);
				connection.setAutoCommit(false);
				connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
			}

			commands.forEach(preExecute(idTranslationTable, connection, caughtExceptions));

			commands.forEach(execute(idTranslationTable, connection, caughtExceptions));

			if (hasModifyingCommands)
			{
				logger.debug(
						"Reseting DB connection setting to: read-only {}, auto-commit {}, transaction-isolation-level {}",
						initialReadOnly, initialAutoCommit,
						getTransactionIsolationLevelString(initialTransactionIsolationLevel));

				connection.setReadOnly(initialReadOnly);
				connection.setAutoCommit(initialAutoCommit);
				connection.setTransactionIsolation(initialTransactionIsolationLevel);
			}

			Map results = new HashMap<>((int) (commands.size() / 0.75) + 1);

			commands.forEach(postExecute(connection, caughtExceptions, results));
			caughtExceptions.forEach((k, v) -> results.put(k, toEntry(v)));

			results.entrySet().stream().sorted(Comparator.comparing(Entry::getKey)).forEach(e ->
			{
				Command command = commands.get(e.getKey());
				BundleEntryComponent result = e.getValue();
				auditLogResult(command, result);
			});

			Bundle result = new Bundle();
			result.setType(BundleType.BATCHRESPONSE);
			results.entrySet().stream().sorted(Comparator.comparing(Entry::getKey)).map(Entry::getValue)
					.forEach(result::addEntry);

			return result;
		}
		catch (Exception e)
		{
			throw exceptionHandler.internalServerErrorBundleTransaction(e);
		}
	}

	private String getTransactionIsolationLevelString(int level)
	{
		return switch (level)
		{
			case Connection.TRANSACTION_NONE -> "NONE";
			case Connection.TRANSACTION_READ_UNCOMMITTED -> "READ_UNCOMMITTED";
			case Connection.TRANSACTION_READ_COMMITTED -> "READ_COMMITTED";
			case Connection.TRANSACTION_REPEATABLE_READ -> "REPEATABLE_READ";
			case Connection.TRANSACTION_SERIALIZABLE -> "SERIALIZABLE";

			default -> "?";
		};
	}

	private Consumer preExecute(Map idTranslationTable, Connection connection,
			Map caughtExceptions)
	{
		return command ->
		{
			try
			{
				if (!caughtExceptions.containsKey(command.getIndex()))
				{
					logger.debug("Running pre-execute of command {} for entry at index {}",
							command.getClass().getName(), command.getIndex());
					command.preExecute(idTranslationTable, connection, validationHelper, snapshotGenerator);
				}
				else
				{
					logger.info("Skipping pre-execute of command {} for entry at index {}, caught exception {}",
							command.getClass().getName(), command.getIndex(),
							caughtExceptions.get(command.getIndex()).getClass().getName() + ": "
									+ caughtExceptions.get(command.getIndex()).getMessage());
				}
			}
			catch (Exception e)
			{
				logger.debug("Error while running pre-execute of command {} for entry at index {}",
						command.getClass().getName(), command.getIndex(), e);
				logger.warn("Error while running pre-execute of command {} for entry at index {}: {} - {}",
						command.getClass().getName(), command.getIndex(), e.getClass().getName(), e.getMessage());

				caughtExceptions.put(command.getIndex(), e);
			}
		};
	}

	private Consumer execute(Map idTranslationTable, Connection connection,
			Map caughtExceptions)
	{
		return command ->
		{
			try
			{
				if (!caughtExceptions.containsKey(command.getIndex()))
				{
					logger.debug("Running execute of command {} for entry at index {}", command.getClass().getName(),
							command.getIndex());
					command.execute(idTranslationTable, connection, validationHelper, snapshotGenerator);
				}
				else
				{
					logger.info("Skipping execute of command {} for entry at index {}, caught exception {}",
							command.getClass().getName(), command.getIndex(),
							caughtExceptions.get(command.getIndex()).getClass().getName() + ": "
									+ caughtExceptions.get(command.getIndex()).getMessage());
				}

				if (!connection.getAutoCommit())
					connection.commit();
			}
			catch (Exception e)
			{
				logger.debug("Error while executing command {}, rolling back transaction for entry at index {}",
						command.getClass().getName(), command.getIndex(), e);
				logger.warn("Error while executing command {}, rolling back transaction for entry at index {}: {} - {}",
						command.getClass().getName(), command.getIndex(), e.getClass().getName(), e.getMessage());

				caughtExceptions.put(command.getIndex(), e);

				try
				{
					if (!connection.getAutoCommit())
						connection.rollback();
				}
				catch (SQLException e1)
				{
					logger.debug(
							"Error while executing command {}, error while rolling back transaction for entry at index {}",
							command.getClass().getName(), command.getIndex(), e1);
					logger.warn(
							"Error while executing command {}, error while rolling back transaction for entry at index {}: {} - {}",
							command.getClass().getName(), command.getIndex(), e1.getClass().getName(), e1.getMessage());

					caughtExceptions.put(command.getIndex(), e1);
				}
			}
		};
	}

	private Consumer postExecute(Connection connection, Map caughtExceptions,
			Map results)
	{
		return command ->
		{
			try
			{
				if (!caughtExceptions.containsKey(command.getIndex()))
				{
					logger.debug("Running post-execute of command {} for entry at index {}",
							command.getClass().getName(), command.getIndex());

					Optional optResult = command.postExecute(connection, eventHandler);
					optResult.ifPresent(result -> results.put(command.getIndex(), result));
				}
				else
				{
					logger.info("Skipping post-execute of command {} for entry at index {}, caught exception {}",
							command.getClass().getName(), command.getIndex(),
							caughtExceptions.get(command.getIndex()).getClass().getName() + ": "
									+ caughtExceptions.get(command.getIndex()).getMessage());
				}
			}
			catch (Exception e)
			{
				logger.debug("Error while running post-execute of command {} for entry at index {}",
						command.getClass().getName(), command.getIndex(), e);
				logger.warn("Error while running post-execute of command {} for entry at index {}: {} - {}",
						command.getClass().getName(), command.getIndex(), e.getClass().getName(), e.getMessage());

				caughtExceptions.put(command.getIndex(), e);
			}
		};
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy