M4 AP-006 Persistenzkonsistenz und Bootstrap-Scope korrigieren
This commit is contained in:
@@ -1,11 +1,5 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.configuration;
|
package de.gecheckt.pdf.umbenenner.adapter.out.configuration;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -17,6 +11,12 @@ import java.nio.file.Paths;
|
|||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Properties-based implementation of {@link ConfigurationPort}.
|
* Properties-based implementation of {@link ConfigurationPort}.
|
||||||
* AP-005: Loads configuration from config/application.properties with environment variable precedence.
|
* AP-005: Loads configuration from config/application.properties with environment variable precedence.
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.fingerprint;
|
package de.gecheckt.pdf.umbenenner.adapter.out.fingerprint;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintPort;
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintResult;
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintSuccess;
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintTechnicalError;
|
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.InvalidPathException;
|
import java.nio.file.InvalidPathException;
|
||||||
@@ -19,6 +11,14 @@ import java.security.NoSuchAlgorithmException;
|
|||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintPort;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintResult;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintSuccess;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintTechnicalError;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SHA-256-based implementation of {@link FingerprintPort}.
|
* SHA-256-based implementation of {@link FingerprintPort}.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.lock;
|
package de.gecheckt.pdf.umbenenner.adapter.out.lock;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockUnavailableException;
|
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockUnavailableException;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.StandardOpenOption;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File-based implementation of {@link RunLockPort} that uses a lock file to prevent concurrent runs.
|
* File-based implementation of {@link RunLockPort} that uses a lock file to prevent concurrent runs.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.pdfextraction;
|
package de.gecheckt.pdf.umbenenner.adapter.out.pdfextraction;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.Loader;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.PdfTextExtractionPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.PdfTextExtractionPort;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||||
import org.apache.pdfbox.Loader;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
||||||
import org.apache.pdfbox.text.PDFTextStripper;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PDFBox-based implementation of {@link PdfTextExtractionPort}.
|
* PDFBox-based implementation of {@link PdfTextExtractionPort}.
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.sourcedocument;
|
package de.gecheckt.pdf.umbenenner.adapter.out.sourcedocument;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentAccessException;
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentCandidatesPort;
|
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentAccessException;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentCandidatesPort;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File-system based implementation of {@link SourceDocumentCandidatesPort}.
|
* File-system based implementation of {@link SourceDocumentCandidatesPort}.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.*;
|
import java.sql.Connection;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
import java.sql.DriverManager;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
|
import java.sql.PreparedStatement;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import java.sql.*;
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentKnownProcessable;
|
||||||
import java.time.Instant;
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
|
||||||
import java.util.Objects;
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordLookupResult;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordRepository;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentTerminalFinalFailure;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentTerminalSuccess;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentUnknown;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.FailureCounters;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceLookupTechnicalFailure;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQLite implementation of {@link DocumentRecordRepository}.
|
* SQLite implementation of {@link DocumentRecordRepository}.
|
||||||
@@ -79,7 +92,7 @@ public class SqliteDocumentRecordRepositoryAdapter implements DocumentRecordRepo
|
|||||||
WHERE fingerprint = ?
|
WHERE fingerprint = ?
|
||||||
""";
|
""";
|
||||||
|
|
||||||
try (Connection connection = DriverManager.getConnection(jdbcUrl);
|
try (Connection connection = getConnection();
|
||||||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
|
|
||||||
statement.setString(1, fingerprint.sha256Hex());
|
statement.setString(1, fingerprint.sha256Hex());
|
||||||
@@ -138,7 +151,7 @@ public class SqliteDocumentRecordRepositoryAdapter implements DocumentRecordRepo
|
|||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""";
|
""";
|
||||||
|
|
||||||
try (Connection connection = DriverManager.getConnection(jdbcUrl);
|
try (Connection connection = getConnection();
|
||||||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
|
|
||||||
statement.setString(1, record.fingerprint().sha256Hex());
|
statement.setString(1, record.fingerprint().sha256Hex());
|
||||||
@@ -197,7 +210,7 @@ public class SqliteDocumentRecordRepositoryAdapter implements DocumentRecordRepo
|
|||||||
WHERE fingerprint = ?
|
WHERE fingerprint = ?
|
||||||
""";
|
""";
|
||||||
|
|
||||||
try (Connection connection = DriverManager.getConnection(jdbcUrl);
|
try (Connection connection = getConnection();
|
||||||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
|
|
||||||
statement.setString(1, record.lastKnownSourceLocator().value());
|
statement.setString(1, record.lastKnownSourceLocator().value());
|
||||||
@@ -282,4 +295,16 @@ public class SqliteDocumentRecordRepositoryAdapter implements DocumentRecordRepo
|
|||||||
public String getJdbcUrl() {
|
public String getJdbcUrl() {
|
||||||
return jdbcUrl;
|
return jdbcUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a connection to the database.
|
||||||
|
* <p>
|
||||||
|
* This method can be overridden by subclasses to provide a shared connection.
|
||||||
|
*
|
||||||
|
* @return a new database connection
|
||||||
|
* @throws SQLException if the connection cannot be established
|
||||||
|
*/
|
||||||
|
protected Connection getConnection() throws SQLException {
|
||||||
|
return DriverManager.getConnection(jdbcUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,24 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
|
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttemptRepository;
|
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttemptRepository;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.sql.*;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQLite implementation of {@link ProcessingAttemptRepository}.
|
* SQLite implementation of {@link ProcessingAttemptRepository}.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -72,7 +77,7 @@ public class SqliteProcessingAttemptRepositoryAdapter implements ProcessingAttem
|
|||||||
WHERE fingerprint = ?
|
WHERE fingerprint = ?
|
||||||
""";
|
""";
|
||||||
|
|
||||||
try (Connection connection = DriverManager.getConnection(jdbcUrl);
|
try (Connection connection = getConnection();
|
||||||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
|
|
||||||
// Enable foreign key enforcement for this connection
|
// Enable foreign key enforcement for this connection
|
||||||
@@ -129,7 +134,7 @@ public class SqliteProcessingAttemptRepositoryAdapter implements ProcessingAttem
|
|||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""";
|
""";
|
||||||
|
|
||||||
try (Connection connection = DriverManager.getConnection(jdbcUrl);
|
try (Connection connection = getConnection();
|
||||||
Statement pragmaStmt = connection.createStatement();
|
Statement pragmaStmt = connection.createStatement();
|
||||||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
|
|
||||||
@@ -198,7 +203,7 @@ public class SqliteProcessingAttemptRepositoryAdapter implements ProcessingAttem
|
|||||||
ORDER BY attempt_number ASC
|
ORDER BY attempt_number ASC
|
||||||
""";
|
""";
|
||||||
|
|
||||||
try (Connection connection = DriverManager.getConnection(jdbcUrl);
|
try (Connection connection = getConnection();
|
||||||
Statement pragmaStmt = connection.createStatement();
|
Statement pragmaStmt = connection.createStatement();
|
||||||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
|
|
||||||
@@ -255,4 +260,16 @@ public class SqliteProcessingAttemptRepositoryAdapter implements ProcessingAttem
|
|||||||
public String getJdbcUrl() {
|
public String getJdbcUrl() {
|
||||||
return jdbcUrl;
|
return jdbcUrl;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Gets a connection to the database.
|
||||||
|
* <p>
|
||||||
|
* This method can be overridden by subclasses to provide a shared connection.
|
||||||
|
*
|
||||||
|
* @return a new database connection
|
||||||
|
* @throws SQLException if the connection cannot be established
|
||||||
|
*/
|
||||||
|
protected Connection getConnection() throws SQLException {
|
||||||
|
return DriverManager.getConnection(jdbcUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitializationPort;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitializationPort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQLite implementation of {@link PersistenceSchemaInitializationPort}.
|
* SQLite implementation of {@link PersistenceSchemaInitializationPort}.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQLite implementation of {@link UnitOfWorkPort}.
|
||||||
|
* <p>
|
||||||
|
* Provides transactional semantics for coordinated writes to both the document record
|
||||||
|
* and processing attempt repositories.
|
||||||
|
*
|
||||||
|
* @since M4-AP-006-fix
|
||||||
|
*/
|
||||||
|
public class SqliteUnitOfWorkAdapter implements UnitOfWorkPort {
|
||||||
|
|
||||||
|
private static final Logger logger = LogManager.getLogger(SqliteUnitOfWorkAdapter.class);
|
||||||
|
|
||||||
|
private final String jdbcUrl;
|
||||||
|
|
||||||
|
public SqliteUnitOfWorkAdapter(String jdbcUrl) {
|
||||||
|
Objects.requireNonNull(jdbcUrl, "jdbcUrl must not be null");
|
||||||
|
if (jdbcUrl.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("jdbcUrl must not be blank");
|
||||||
|
}
|
||||||
|
this.jdbcUrl = jdbcUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeInTransaction(Consumer<TransactionOperations> operations) {
|
||||||
|
Objects.requireNonNull(operations, "operations must not be null");
|
||||||
|
|
||||||
|
Connection connection = null;
|
||||||
|
try {
|
||||||
|
connection = DriverManager.getConnection(jdbcUrl);
|
||||||
|
connection.setAutoCommit(false);
|
||||||
|
|
||||||
|
TransactionOperationsImpl txOps = new TransactionOperationsImpl(connection);
|
||||||
|
operations.accept(txOps);
|
||||||
|
|
||||||
|
connection.commit();
|
||||||
|
logger.debug("Transaction committed successfully");
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (connection != null) {
|
||||||
|
try {
|
||||||
|
connection.rollback();
|
||||||
|
logger.debug("Transaction rolled back due to error: {}", e.getMessage());
|
||||||
|
} catch (SQLException rollbackEx) {
|
||||||
|
logger.error("Failed to rollback transaction: {}", rollbackEx.getMessage(), rollbackEx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new DocumentPersistenceException("Transaction failed: " + e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
if (connection != null) {
|
||||||
|
try {
|
||||||
|
connection.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.warn("Failed to close connection: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TransactionOperationsImpl implements TransactionOperations {
|
||||||
|
private final Connection connection;
|
||||||
|
|
||||||
|
TransactionOperationsImpl(Connection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveProcessingAttempt(ProcessingAttempt attempt) {
|
||||||
|
try {
|
||||||
|
// Reuse the existing repository logic but with shared connection
|
||||||
|
SqliteProcessingAttemptRepositoryAdapter repo =
|
||||||
|
new SqliteProcessingAttemptRepositoryAdapter(jdbcUrl) {
|
||||||
|
@Override
|
||||||
|
protected Connection getConnection() throws SQLException {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
repo.save(attempt);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new DocumentPersistenceException("Failed to save processing attempt: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createDocumentRecord(DocumentRecord record) {
|
||||||
|
try {
|
||||||
|
// Reuse the existing repository logic but with shared connection
|
||||||
|
SqliteDocumentRecordRepositoryAdapter repo =
|
||||||
|
new SqliteDocumentRecordRepositoryAdapter(jdbcUrl) {
|
||||||
|
@Override
|
||||||
|
protected Connection getConnection() throws SQLException {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
repo.create(record);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new DocumentPersistenceException("Failed to create document record: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDocumentRecord(DocumentRecord record) {
|
||||||
|
try {
|
||||||
|
// Reuse the existing repository logic but with shared connection
|
||||||
|
SqliteDocumentRecordRepositoryAdapter repo =
|
||||||
|
new SqliteDocumentRecordRepositoryAdapter(jdbcUrl) {
|
||||||
|
@Override
|
||||||
|
protected Connection getConnection() throws SQLException {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
repo.update(record);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new DocumentPersistenceException("Failed to update document record: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.configuration;
|
package de.gecheckt.pdf.umbenenner.adapter.out.configuration;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import org.junit.jupiter.api.Test;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.out.configuration.PropertiesConfigurationPortAdapter;
|
|
||||||
|
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link PropertiesConfigurationPortAdapter}.
|
* Unit tests for {@link PropertiesConfigurationPortAdapter}.
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.fingerprint;
|
package de.gecheckt.pdf.umbenenner.adapter.out.fingerprint;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintResult;
|
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintResult;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintSuccess;
|
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintSuccess;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintTechnicalError;
|
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintTechnicalError;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link Sha256FingerprintAdapter}.
|
* Unit tests for {@link Sha256FingerprintAdapter}.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.lock;
|
package de.gecheckt.pdf.umbenenner.adapter.out.lock;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.out.lock.FilesystemRunLockPortAdapter;
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockUnavailableException;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import org.junit.jupiter.api.Test;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockUnavailableException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link FilesystemRunLockPortAdapter}.
|
* Unit tests for {@link FilesystemRunLockPortAdapter}.
|
||||||
|
|||||||
@@ -1,23 +1,5 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.pdfextraction;
|
package de.gecheckt.pdf.umbenenner.adapter.out.pdfextraction;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.out.pdfextraction.PdfTextExtractionPortAdapter;
|
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult;
|
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError;
|
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.attribute.PosixFilePermission;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
@@ -25,6 +7,24 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.PosixFilePermission;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link PdfTextExtractionPortAdapter}.
|
* Tests for {@link PdfTextExtractionPortAdapter}.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.sourcedocument;
|
package de.gecheckt.pdf.umbenenner.adapter.out.sourcedocument;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.out.sourcedocument.SourceDocumentCandidatesPortAdapter;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentAccessException;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import org.junit.jupiter.api.Test;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentAccessException;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link SourceDocumentCandidatesPortAdapter}.
|
* Tests for {@link SourceDocumentCandidatesPortAdapter}.
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.*;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
|
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.*;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentKnownProcessable;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordLookupResult;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentTerminalSuccess;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentUnknown;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.FailureCounters;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link SqliteDocumentRecordRepositoryAdapter}.
|
* Tests for {@link SqliteDocumentRecordRepositoryAdapter}.
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.*;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
|
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
import java.nio.file.Path;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
|
||||||
import java.time.Instant;
|
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
|
||||||
import java.time.temporal.ChronoUnit;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
import java.util.List;
|
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
|
||||||
import java.sql.Connection;
|
import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
||||||
import java.sql.DriverManager;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link SqliteProcessingAttemptRepositoryAdapter}.
|
* Tests for {@link SqliteProcessingAttemptRepositoryAdapter}.
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import org.junit.jupiter.api.Test;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
@@ -13,8 +12,10 @@ import java.sql.SQLException;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link SqliteSchemaInitializationAdapter}.
|
* Unit tests for {@link SqliteSchemaInitializationAdapter}.
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package de.gecheckt.pdf.umbenenner.application.port.out;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Port for executing multiple repository operations within a single unit of work.
|
||||||
|
* <p>
|
||||||
|
* Ensures that related persistence operations (such as saving a processing attempt
|
||||||
|
* and updating a document record) are executed atomically.
|
||||||
|
*
|
||||||
|
* @since M4-AP-006-fix
|
||||||
|
*/
|
||||||
|
public interface UnitOfWorkPort {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the given operations within a single unit of work.
|
||||||
|
* <p>
|
||||||
|
* If any operation fails, all changes are rolled back and the exception is propagated.
|
||||||
|
*
|
||||||
|
* @param operations the operations to execute; must not be null
|
||||||
|
* @throws DocumentPersistenceException if any operation fails
|
||||||
|
*/
|
||||||
|
void executeInTransaction(Consumer<TransactionOperations> operations);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operations available within a transaction.
|
||||||
|
*/
|
||||||
|
interface TransactionOperations {
|
||||||
|
void saveProcessingAttempt(ProcessingAttempt attempt);
|
||||||
|
void createDocumentRecord(DocumentRecord record);
|
||||||
|
void updateDocumentRecord(DocumentRecord record);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.FailureCounters;
|
|||||||
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceLookupTechnicalFailure;
|
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceLookupTechnicalFailure;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
|
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttemptRepository;
|
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttemptRepository;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
||||||
@@ -66,12 +67,9 @@ import java.util.Objects;
|
|||||||
* <h2>Persistence consistency</h2>
|
* <h2>Persistence consistency</h2>
|
||||||
* <p>
|
* <p>
|
||||||
* For every identified document, both the processing attempt and the master record are
|
* For every identified document, both the processing attempt and the master record are
|
||||||
* written in sequence. If either write fails, the failure is logged and the batch run
|
* written atomically using a unit of work pattern. If either write fails, both writes
|
||||||
* continues with the next candidate. No partial state is intentionally left; if the
|
* are rolled back and the failure is logged. The batch run continues with the next
|
||||||
* attempt write succeeds but the master record write fails, the inconsistency is bounded
|
* candidate.
|
||||||
* to that one document and is logged clearly. True transactionality across two separate
|
|
||||||
* repository calls is not available without a larger architectural change; this is
|
|
||||||
* documented as a known limitation of the M4 scope.
|
|
||||||
*
|
*
|
||||||
* <h2>Pre-fingerprint failures</h2>
|
* <h2>Pre-fingerprint failures</h2>
|
||||||
* <p>
|
* <p>
|
||||||
@@ -87,6 +85,7 @@ public class M4DocumentProcessor {
|
|||||||
|
|
||||||
private final DocumentRecordRepository documentRecordRepository;
|
private final DocumentRecordRepository documentRecordRepository;
|
||||||
private final ProcessingAttemptRepository processingAttemptRepository;
|
private final ProcessingAttemptRepository processingAttemptRepository;
|
||||||
|
private final UnitOfWorkPort unitOfWorkPort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the M4 document processor with the required persistence ports.
|
* Creates the M4 document processor with the required persistence ports.
|
||||||
@@ -95,15 +94,20 @@ public class M4DocumentProcessor {
|
|||||||
* must not be null
|
* must not be null
|
||||||
* @param processingAttemptRepository port for writing and reading the attempt history;
|
* @param processingAttemptRepository port for writing and reading the attempt history;
|
||||||
* must not be null
|
* must not be null
|
||||||
|
* @param unitOfWorkPort port for executing operations atomically;
|
||||||
|
* must not be null
|
||||||
* @throws NullPointerException if any parameter is null
|
* @throws NullPointerException if any parameter is null
|
||||||
*/
|
*/
|
||||||
public M4DocumentProcessor(
|
public M4DocumentProcessor(
|
||||||
DocumentRecordRepository documentRecordRepository,
|
DocumentRecordRepository documentRecordRepository,
|
||||||
ProcessingAttemptRepository processingAttemptRepository) {
|
ProcessingAttemptRepository processingAttemptRepository,
|
||||||
|
UnitOfWorkPort unitOfWorkPort) {
|
||||||
this.documentRecordRepository =
|
this.documentRecordRepository =
|
||||||
Objects.requireNonNull(documentRecordRepository, "documentRecordRepository must not be null");
|
Objects.requireNonNull(documentRecordRepository, "documentRecordRepository must not be null");
|
||||||
this.processingAttemptRepository =
|
this.processingAttemptRepository =
|
||||||
Objects.requireNonNull(processingAttemptRepository, "processingAttemptRepository must not be null");
|
Objects.requireNonNull(processingAttemptRepository, "processingAttemptRepository must not be null");
|
||||||
|
this.unitOfWorkPort =
|
||||||
|
Objects.requireNonNull(unitOfWorkPort, "unitOfWorkPort must not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -329,22 +333,24 @@ public class M4DocumentProcessor {
|
|||||||
false // not retryable
|
false // not retryable
|
||||||
);
|
);
|
||||||
|
|
||||||
// Write attempt first, then update master record
|
// Write attempt and master record atomically
|
||||||
processingAttemptRepository.save(skipAttempt);
|
unitOfWorkPort.executeInTransaction(txOps -> {
|
||||||
|
txOps.saveProcessingAttempt(skipAttempt);
|
||||||
|
|
||||||
// Update master record: only updatedAt changes; status and counters stay the same
|
// Update master record: only updatedAt changes; status and counters stay the same
|
||||||
DocumentRecord updatedRecord = new DocumentRecord(
|
DocumentRecord updatedRecord = new DocumentRecord(
|
||||||
existingRecord.fingerprint(),
|
existingRecord.fingerprint(),
|
||||||
new SourceDocumentLocator(candidate.locator().value()),
|
new SourceDocumentLocator(candidate.locator().value()),
|
||||||
candidate.uniqueIdentifier(),
|
candidate.uniqueIdentifier(),
|
||||||
existingRecord.overallStatus(), // terminal status unchanged
|
existingRecord.overallStatus(), // terminal status unchanged
|
||||||
existingRecord.failureCounters(), // counters unchanged for skip
|
existingRecord.failureCounters(), // counters unchanged for skip
|
||||||
existingRecord.lastFailureInstant(),
|
existingRecord.lastFailureInstant(),
|
||||||
existingRecord.lastSuccessInstant(),
|
existingRecord.lastSuccessInstant(),
|
||||||
existingRecord.createdAt(),
|
existingRecord.createdAt(),
|
||||||
now // updatedAt = now
|
now // updatedAt = now
|
||||||
);
|
);
|
||||||
documentRecordRepository.update(updatedRecord);
|
txOps.updateDocumentRecord(updatedRecord);
|
||||||
|
});
|
||||||
|
|
||||||
LOG.debug("Skip attempt #{} persisted for '{}' with status {}.",
|
LOG.debug("Skip attempt #{} persisted for '{}' with status {}.",
|
||||||
attemptNumber, candidate.uniqueIdentifier(), skipStatus);
|
attemptNumber, candidate.uniqueIdentifier(), skipStatus);
|
||||||
@@ -401,9 +407,11 @@ public class M4DocumentProcessor {
|
|||||||
now // updatedAt
|
now // updatedAt
|
||||||
);
|
);
|
||||||
|
|
||||||
// Persist attempt first, then master record
|
// Persist attempt and master record atomically
|
||||||
processingAttemptRepository.save(attempt);
|
unitOfWorkPort.executeInTransaction(txOps -> {
|
||||||
documentRecordRepository.create(newRecord);
|
txOps.saveProcessingAttempt(attempt);
|
||||||
|
txOps.createDocumentRecord(newRecord);
|
||||||
|
});
|
||||||
|
|
||||||
LOG.info("New document '{}' processed: status={}, contentErrors={}, transientErrors={}.",
|
LOG.info("New document '{}' processed: status={}, contentErrors={}, transientErrors={}.",
|
||||||
candidate.uniqueIdentifier(),
|
candidate.uniqueIdentifier(),
|
||||||
@@ -466,9 +474,11 @@ public class M4DocumentProcessor {
|
|||||||
now // updatedAt
|
now // updatedAt
|
||||||
);
|
);
|
||||||
|
|
||||||
// Persist attempt first, then master record
|
// Persist attempt and master record atomically
|
||||||
processingAttemptRepository.save(attempt);
|
unitOfWorkPort.executeInTransaction(txOps -> {
|
||||||
documentRecordRepository.update(updatedRecord);
|
txOps.saveProcessingAttempt(attempt);
|
||||||
|
txOps.updateDocumentRecord(updatedRecord);
|
||||||
|
});
|
||||||
|
|
||||||
LOG.info("Known document '{}' processed: status={}, contentErrors={}, transientErrors={}.",
|
LOG.info("Known document '{}' processed: status={}, contentErrors={}, transientErrors={}.",
|
||||||
candidate.uniqueIdentifier(),
|
candidate.uniqueIdentifier(),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.FailureCounters;
|
|||||||
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceLookupTechnicalFailure;
|
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceLookupTechnicalFailure;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
|
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttemptRepository;
|
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttemptRepository;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
||||||
@@ -32,6 +33,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
@@ -56,6 +58,7 @@ class M4DocumentProcessorTest {
|
|||||||
|
|
||||||
private CapturingDocumentRecordRepository recordRepo;
|
private CapturingDocumentRecordRepository recordRepo;
|
||||||
private CapturingProcessingAttemptRepository attemptRepo;
|
private CapturingProcessingAttemptRepository attemptRepo;
|
||||||
|
private CapturingUnitOfWorkPort unitOfWorkPort;
|
||||||
private M4DocumentProcessor processor;
|
private M4DocumentProcessor processor;
|
||||||
|
|
||||||
private SourceDocumentCandidate candidate;
|
private SourceDocumentCandidate candidate;
|
||||||
@@ -67,7 +70,8 @@ class M4DocumentProcessorTest {
|
|||||||
void setUp() {
|
void setUp() {
|
||||||
recordRepo = new CapturingDocumentRecordRepository();
|
recordRepo = new CapturingDocumentRecordRepository();
|
||||||
attemptRepo = new CapturingProcessingAttemptRepository();
|
attemptRepo = new CapturingProcessingAttemptRepository();
|
||||||
processor = new M4DocumentProcessor(recordRepo, attemptRepo);
|
unitOfWorkPort = new CapturingUnitOfWorkPort(recordRepo, attemptRepo);
|
||||||
|
processor = new M4DocumentProcessor(recordRepo, attemptRepo, unitOfWorkPort);
|
||||||
|
|
||||||
candidate = new SourceDocumentCandidate(
|
candidate = new SourceDocumentCandidate(
|
||||||
"test.pdf", 1024L, new SourceDocumentLocator("/tmp/test.pdf"));
|
"test.pdf", 1024L, new SourceDocumentLocator("/tmp/test.pdf"));
|
||||||
@@ -321,8 +325,8 @@ class M4DocumentProcessorTest {
|
|||||||
@Test
|
@Test
|
||||||
void process_persistenceWriteFailure_doesNotThrow_batchContinues() {
|
void process_persistenceWriteFailure_doesNotThrow_batchContinues() {
|
||||||
recordRepo.setLookupResult(new DocumentUnknown());
|
recordRepo.setLookupResult(new DocumentUnknown());
|
||||||
// Make the attempt save throw
|
// Make the unit of work throw
|
||||||
attemptRepo.failOnSave = true;
|
unitOfWorkPort.failOnExecute = true;
|
||||||
|
|
||||||
DocumentProcessingOutcome m3Outcome = new PreCheckPassed(
|
DocumentProcessingOutcome m3Outcome = new PreCheckPassed(
|
||||||
candidate, new PdfExtractionSuccess("text", new PdfPageCount(1)));
|
candidate, new PdfExtractionSuccess("text", new PdfPageCount(1)));
|
||||||
@@ -422,4 +426,45 @@ class M4DocumentProcessorTest {
|
|||||||
return List.copyOf(savedAttempts);
|
return List.copyOf(savedAttempts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class CapturingUnitOfWorkPort implements UnitOfWorkPort {
|
||||||
|
private final CapturingDocumentRecordRepository recordRepo;
|
||||||
|
private final CapturingProcessingAttemptRepository attemptRepo;
|
||||||
|
boolean failOnExecute = false;
|
||||||
|
Consumer<TransactionOperations> lastOperations = null;
|
||||||
|
|
||||||
|
CapturingUnitOfWorkPort(CapturingDocumentRecordRepository recordRepo,
|
||||||
|
CapturingProcessingAttemptRepository attemptRepo) {
|
||||||
|
this.recordRepo = recordRepo;
|
||||||
|
this.attemptRepo = attemptRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeInTransaction(Consumer<TransactionOperations> operations) {
|
||||||
|
this.lastOperations = operations;
|
||||||
|
if (failOnExecute) {
|
||||||
|
throw new DocumentPersistenceException("Simulated transaction failure");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the operations with mock transaction operations that delegate to repos
|
||||||
|
TransactionOperations mockOps = new TransactionOperations() {
|
||||||
|
@Override
|
||||||
|
public void saveProcessingAttempt(ProcessingAttempt attempt) {
|
||||||
|
attemptRepo.savedAttempts.add(attempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createDocumentRecord(DocumentRecord record) {
|
||||||
|
recordRepo.createdRecords.add(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDocumentRecord(DocumentRecord record) {
|
||||||
|
recordRepo.updatedRecords.add(record);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
operations.accept(mockOps);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
|
|||||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockUnavailableException;
|
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockUnavailableException;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentAccessException;
|
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentAccessException;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentCandidatesPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentCandidatesPort;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
|
||||||
import de.gecheckt.pdf.umbenenner.application.service.M4DocumentProcessor;
|
import de.gecheckt.pdf.umbenenner.application.service.M4DocumentProcessor;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
@@ -39,6 +40,7 @@ import java.time.Instant;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
@@ -615,7 +617,7 @@ class BatchRunProcessingUseCaseTest {
|
|||||||
*/
|
*/
|
||||||
private static class NoOpM4DocumentProcessor extends M4DocumentProcessor {
|
private static class NoOpM4DocumentProcessor extends M4DocumentProcessor {
|
||||||
NoOpM4DocumentProcessor() {
|
NoOpM4DocumentProcessor() {
|
||||||
super(new NoOpDocumentRecordRepository(), new NoOpProcessingAttemptRepository());
|
super(new NoOpDocumentRecordRepository(), new NoOpProcessingAttemptRepository(), new NoOpUnitOfWorkPort());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,7 +628,7 @@ class BatchRunProcessingUseCaseTest {
|
|||||||
private int processCallCount = 0;
|
private int processCallCount = 0;
|
||||||
|
|
||||||
TrackingM4DocumentProcessor() {
|
TrackingM4DocumentProcessor() {
|
||||||
super(new NoOpDocumentRecordRepository(), new NoOpProcessingAttemptRepository());
|
super(new NoOpDocumentRecordRepository(), new NoOpProcessingAttemptRepository(), new NoOpUnitOfWorkPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -692,4 +694,28 @@ class BatchRunProcessingUseCaseTest {
|
|||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** No-op UnitOfWorkPort for use in test M4DocumentProcessor instances. */
|
||||||
|
private static class NoOpUnitOfWorkPort implements UnitOfWorkPort {
|
||||||
|
@Override
|
||||||
|
public void executeInTransaction(Consumer<TransactionOperations> operations) {
|
||||||
|
// No-op - just execute the operations directly without transaction
|
||||||
|
operations.accept(new TransactionOperations() {
|
||||||
|
@Override
|
||||||
|
public void saveProcessingAttempt(ProcessingAttempt attempt) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createDocumentRecord(DocumentRecord record) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDocumentRecord(DocumentRecord record) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.bootstrap;
|
package de.gecheckt.pdf.umbenenner.bootstrap;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@@ -12,6 +17,7 @@ import de.gecheckt.pdf.umbenenner.adapter.out.sourcedocument.SourceDocumentCandi
|
|||||||
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteDocumentRecordRepositoryAdapter;
|
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteDocumentRecordRepositoryAdapter;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteProcessingAttemptRepositoryAdapter;
|
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteProcessingAttemptRepositoryAdapter;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteSchemaInitializationAdapter;
|
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteSchemaInitializationAdapter;
|
||||||
|
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteUnitOfWorkAdapter;
|
||||||
import de.gecheckt.pdf.umbenenner.application.config.InvalidStartConfigurationException;
|
import de.gecheckt.pdf.umbenenner.application.config.InvalidStartConfigurationException;
|
||||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
||||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfigurationValidator;
|
import de.gecheckt.pdf.umbenenner.application.config.StartConfigurationValidator;
|
||||||
@@ -24,16 +30,12 @@ import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintPort;
|
|||||||
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitializationPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitializationPort;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttemptRepository;
|
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttemptRepository;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
|
||||||
import de.gecheckt.pdf.umbenenner.application.service.M4DocumentProcessor;
|
import de.gecheckt.pdf.umbenenner.application.service.M4DocumentProcessor;
|
||||||
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultBatchRunProcessingUseCase;
|
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultBatchRunProcessingUseCase;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manual bootstrap runner that constructs the object graph and drives the startup flow.
|
* Manual bootstrap runner that constructs the object graph and drives the startup flow.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -65,6 +67,7 @@ import java.util.UUID;
|
|||||||
* <li>{@link SqliteSchemaInitializationAdapter} — schema initialisation at startup.</li>
|
* <li>{@link SqliteSchemaInitializationAdapter} — schema initialisation at startup.</li>
|
||||||
* <li>{@link SqliteDocumentRecordRepositoryAdapter} — document master record CRUD.</li>
|
* <li>{@link SqliteDocumentRecordRepositoryAdapter} — document master record CRUD.</li>
|
||||||
* <li>{@link SqliteProcessingAttemptRepositoryAdapter} — attempt history CRUD.</li>
|
* <li>{@link SqliteProcessingAttemptRepositoryAdapter} — attempt history CRUD.</li>
|
||||||
|
* <li>{@link SqliteUnitOfWorkAdapter} — atomic persistence operations.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @since M2 (extended in M4-AP-006)
|
* @since M2 (extended in M4-AP-006)
|
||||||
@@ -135,6 +138,7 @@ public class BootstrapRunner {
|
|||||||
* <li>{@link Sha256FingerprintAdapter} for SHA-256 content fingerprinting.</li>
|
* <li>{@link Sha256FingerprintAdapter} for SHA-256 content fingerprinting.</li>
|
||||||
* <li>{@link SqliteDocumentRecordRepositoryAdapter} for document master record CRUD.</li>
|
* <li>{@link SqliteDocumentRecordRepositoryAdapter} for document master record CRUD.</li>
|
||||||
* <li>{@link SqliteProcessingAttemptRepositoryAdapter} for attempt history CRUD.</li>
|
* <li>{@link SqliteProcessingAttemptRepositoryAdapter} for attempt history CRUD.</li>
|
||||||
|
* <li>{@link SqliteUnitOfWorkAdapter} for atomic persistence operations.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* Schema initialisation is performed in {@link #run()} before the use case is created,
|
* Schema initialisation is performed in {@link #run()} before the use case is created,
|
||||||
@@ -151,8 +155,10 @@ public class BootstrapRunner {
|
|||||||
new SqliteDocumentRecordRepositoryAdapter(jdbcUrl);
|
new SqliteDocumentRecordRepositoryAdapter(jdbcUrl);
|
||||||
ProcessingAttemptRepository processingAttemptRepository =
|
ProcessingAttemptRepository processingAttemptRepository =
|
||||||
new SqliteProcessingAttemptRepositoryAdapter(jdbcUrl);
|
new SqliteProcessingAttemptRepositoryAdapter(jdbcUrl);
|
||||||
|
UnitOfWorkPort unitOfWorkPort =
|
||||||
|
new SqliteUnitOfWorkAdapter(jdbcUrl);
|
||||||
M4DocumentProcessor m4Processor =
|
M4DocumentProcessor m4Processor =
|
||||||
new M4DocumentProcessor(documentRecordRepository, processingAttemptRepository);
|
new M4DocumentProcessor(documentRecordRepository, processingAttemptRepository, unitOfWorkPort);
|
||||||
return new DefaultBatchRunProcessingUseCase(
|
return new DefaultBatchRunProcessingUseCase(
|
||||||
config,
|
config,
|
||||||
lock,
|
lock,
|
||||||
|
|||||||
Reference in New Issue
Block a user