M4 AP-007 Scope bereinigen und Startfehler-Test ergänzen
This commit is contained in:
@@ -16,6 +16,7 @@ import de.gecheckt.pdf.umbenenner.adapter.out.pdfextraction.PdfTextExtractionPor
|
||||
import de.gecheckt.pdf.umbenenner.adapter.out.sourcedocument.SourceDocumentCandidatesPortAdapter;
|
||||
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.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.StartConfiguration;
|
||||
@@ -26,6 +27,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordRepository;
|
||||
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.ProcessingAttemptRepository;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
|
||||
@@ -40,6 +42,7 @@ import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
||||
* Responsibilities:
|
||||
* <ol>
|
||||
* <li>Load and validate the startup configuration.</li>
|
||||
* <li>Initialise the SQLite persistence schema (M4-AP-007).</li>
|
||||
* <li>Resolve the run-lock file path (with default fallback).</li>
|
||||
* <li>Create and wire all ports and adapters via configured factories.</li>
|
||||
* <li>Start the CLI adapter and execute the batch use case.</li>
|
||||
@@ -56,19 +59,22 @@ import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
||||
* during the run.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>M4 wiring (AP-006)</h2>
|
||||
* <h2>M4 wiring (AP-006 / AP-007)</h2>
|
||||
* <p>
|
||||
* The production constructor wires the following M4 adapters via the UseCaseFactory:
|
||||
* The production constructor wires the following M4 adapters:
|
||||
* <ul>
|
||||
* <li>{@link SqliteSchemaInitializationAdapter} — SQLite schema DDL at startup (AP-007).</li>
|
||||
* <li>{@link Sha256FingerprintAdapter} — SHA-256 content fingerprinting.</li>
|
||||
* <li>{@link SqliteDocumentRecordRepositoryAdapter} — document master record CRUD.</li>
|
||||
* <li>{@link SqliteProcessingAttemptRepositoryAdapter} — attempt history CRUD.</li>
|
||||
* <li>{@link SqliteUnitOfWorkAdapter} — atomic persistence operations.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Schema initialisation is AP-007 responsibility, not performed in AP-006.
|
||||
* Schema initialisation is performed once in {@link #run()} before the batch loop starts
|
||||
* (AP-007). A {@link DocumentPersistenceException} during schema initialisation is treated
|
||||
* as a hard startup failure and results in exit code 1.
|
||||
*
|
||||
* @since M2 (extended in M4-AP-006)
|
||||
* @since M2 (extended in M4-AP-006, M4-AP-007)
|
||||
*/
|
||||
public class BootstrapRunner {
|
||||
|
||||
@@ -77,6 +83,7 @@ public class BootstrapRunner {
|
||||
private final ConfigurationPortFactory configPortFactory;
|
||||
private final RunLockPortFactory runLockPortFactory;
|
||||
private final ValidatorFactory validatorFactory;
|
||||
private final SchemaInitializationPortFactory schemaInitPortFactory;
|
||||
private final UseCaseFactory useCaseFactory;
|
||||
private final CommandFactory commandFactory;
|
||||
|
||||
@@ -104,6 +111,14 @@ public class BootstrapRunner {
|
||||
StartConfigurationValidator create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional interface for creating a PersistenceSchemaInitializationPort from the JDBC URL.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SchemaInitializationPortFactory {
|
||||
PersistenceSchemaInitializationPort create(String jdbcUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional interface for creating a BatchRunProcessingUseCase.
|
||||
* <p>
|
||||
@@ -134,17 +149,20 @@ public class BootstrapRunner {
|
||||
* <li>{@link SourceDocumentCandidatesPortAdapter} for PDF candidate discovery.</li>
|
||||
* <li>{@link PdfTextExtractionPortAdapter} for PDFBox-based text and page count extraction.</li>
|
||||
* <li>{@link Sha256FingerprintAdapter} for SHA-256 content fingerprinting.</li>
|
||||
* <li>{@link SqliteSchemaInitializationAdapter} for SQLite schema DDL at startup (AP-007).</li>
|
||||
* <li>{@link SqliteDocumentRecordRepositoryAdapter} for document master record CRUD.</li>
|
||||
* <li>{@link SqliteProcessingAttemptRepositoryAdapter} for attempt history CRUD.</li>
|
||||
* <li>{@link SqliteUnitOfWorkAdapter} for atomic persistence operations.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Schema initialisation is AP-007 responsibility and is NOT performed here.
|
||||
* Schema initialisation is performed explicitly in {@link #run()} before the batch loop
|
||||
* begins (AP-007). Failure during initialisation aborts the run with exit code 1.
|
||||
*/
|
||||
public BootstrapRunner() {
|
||||
this.configPortFactory = PropertiesConfigurationPortAdapter::new;
|
||||
this.runLockPortFactory = FilesystemRunLockPortAdapter::new;
|
||||
this.validatorFactory = StartConfigurationValidator::new;
|
||||
this.schemaInitPortFactory = SqliteSchemaInitializationAdapter::new;
|
||||
this.useCaseFactory = (config, lock) -> {
|
||||
String jdbcUrl = buildJdbcUrl(config);
|
||||
FingerprintPort fingerprintPort = new Sha256FingerprintAdapter();
|
||||
@@ -170,20 +188,23 @@ public class BootstrapRunner {
|
||||
/**
|
||||
* Creates the BootstrapRunner with custom factories for testing.
|
||||
*
|
||||
* @param configPortFactory factory for creating ConfigurationPort instances
|
||||
* @param runLockPortFactory factory for creating RunLockPort instances
|
||||
* @param validatorFactory factory for creating StartConfigurationValidator instances
|
||||
* @param useCaseFactory factory for creating BatchRunProcessingUseCase instances
|
||||
* @param commandFactory factory for creating SchedulerBatchCommand instances
|
||||
* @param configPortFactory factory for creating ConfigurationPort instances
|
||||
* @param runLockPortFactory factory for creating RunLockPort instances
|
||||
* @param validatorFactory factory for creating StartConfigurationValidator instances
|
||||
* @param schemaInitPortFactory factory for creating PersistenceSchemaInitializationPort instances (AP-007)
|
||||
* @param useCaseFactory factory for creating BatchRunProcessingUseCase instances
|
||||
* @param commandFactory factory for creating SchedulerBatchCommand instances
|
||||
*/
|
||||
public BootstrapRunner(ConfigurationPortFactory configPortFactory,
|
||||
RunLockPortFactory runLockPortFactory,
|
||||
ValidatorFactory validatorFactory,
|
||||
SchemaInitializationPortFactory schemaInitPortFactory,
|
||||
UseCaseFactory useCaseFactory,
|
||||
CommandFactory commandFactory) {
|
||||
this.configPortFactory = configPortFactory;
|
||||
this.runLockPortFactory = runLockPortFactory;
|
||||
this.validatorFactory = validatorFactory;
|
||||
this.schemaInitPortFactory = schemaInitPortFactory;
|
||||
this.useCaseFactory = useCaseFactory;
|
||||
this.commandFactory = commandFactory;
|
||||
}
|
||||
@@ -191,13 +212,27 @@ public class BootstrapRunner {
|
||||
/**
|
||||
* Runs the application startup sequence.
|
||||
* <p>
|
||||
* M4 additions:
|
||||
* <ul>
|
||||
* <li>Derives the SQLite JDBC URL from the configured {@code sqlite.file} path.</li>
|
||||
* <li>Creates the M4-aware use case via the {@link UseCaseFactory}, which wires persistence ports.</li>
|
||||
* </ul>
|
||||
* M4 startup flow (AP-007):
|
||||
* <ol>
|
||||
* <li>Load configuration via {@link ConfigurationPort}.</li>
|
||||
* <li>Validate the configuration via {@link StartConfigurationValidator}; validation
|
||||
* includes checking that the {@code sqlite.file} parent directory exists or is
|
||||
* technically creatable.</li>
|
||||
* <li>Initialise the SQLite persistence schema explicitly via
|
||||
* {@link PersistenceSchemaInitializationPort}. This step happens once, before the
|
||||
* batch document loop begins. A {@link DocumentPersistenceException} here is a hard
|
||||
* startup failure and causes exit code 1.</li>
|
||||
* <li>Resolve the run-lock file path, apply default if not configured.</li>
|
||||
* <li>Create the batch use case with all M4 adapters wired.</li>
|
||||
* <li>Execute the CLI command and map the outcome to an exit code.</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* Document-level failures during the batch loop (step 6) are not startup failures and
|
||||
* do not change the exit code as long as the run itself completes without a hard
|
||||
* infrastructure error.
|
||||
*
|
||||
* @return exit code: 0 for success, 1 for invalid configuration or unexpected bootstrap failure
|
||||
* @return exit code: 0 for a technically completed run, 1 for any hard startup or
|
||||
* bootstrap failure (configuration invalid, schema init failed, etc.)
|
||||
*/
|
||||
public int run() {
|
||||
LOG.info("Bootstrap flow started.");
|
||||
@@ -208,11 +243,18 @@ public class BootstrapRunner {
|
||||
// Step 2: Load configuration
|
||||
var config = configPort.loadConfiguration();
|
||||
|
||||
// Step 3: Validate configuration
|
||||
// Step 3: Validate configuration.
|
||||
// Includes checking that sqlite.file parent directory exists or is creatable.
|
||||
StartConfigurationValidator validator = validatorFactory.create();
|
||||
validator.validate(config);
|
||||
|
||||
// Step 4: Resolve lock file path – apply default if not configured
|
||||
// Step 4 (M4-AP-007): Initialise SQLite persistence schema before the batch loop.
|
||||
// Must happen once at startup; failure here is a hard bootstrap error → exit code 1.
|
||||
String jdbcUrl = buildJdbcUrl(config);
|
||||
PersistenceSchemaInitializationPort schemaInitPort = schemaInitPortFactory.create(jdbcUrl);
|
||||
schemaInitPort.initializeSchema();
|
||||
|
||||
// Step 5: Resolve lock file path – apply default if not configured
|
||||
Path lockFilePath = config.runtimeLockFile();
|
||||
if (lockFilePath == null || lockFilePath.toString().isBlank()) {
|
||||
lockFilePath = Paths.get("pdf-umbenenner.lock");
|
||||
@@ -221,21 +263,20 @@ public class BootstrapRunner {
|
||||
}
|
||||
RunLockPort runLockPort = runLockPortFactory.create(lockFilePath);
|
||||
|
||||
// Step 5: Create the batch run context
|
||||
// Step 6: Create the batch run context
|
||||
RunId runId = new RunId(UUID.randomUUID().toString());
|
||||
BatchRunContext runContext = new BatchRunContext(runId, Instant.now());
|
||||
LOG.info("Batch run started. RunId: {}", runId);
|
||||
|
||||
// Step 6: Create the use case with the validated config and run lock.
|
||||
// Step 7: Create the use case with the validated config and run lock.
|
||||
// Config is passed directly; the use case does not re-read the properties file.
|
||||
// Adapters (source document port, PDF extraction port, M4 ports) are wired by the factory.
|
||||
// Schema initialization is AP-007 responsibility, not performed in AP-006.
|
||||
BatchRunProcessingUseCase useCase = useCaseFactory.create(config, runLockPort);
|
||||
|
||||
// Step 7: Create the CLI command adapter with the use case
|
||||
// Step 8: Create the CLI command adapter with the use case
|
||||
SchedulerBatchCommand command = commandFactory.create(useCase);
|
||||
|
||||
// Step 8: Execute the command with the run context and handle the outcome
|
||||
// Step 9: Execute the command with the run context and handle the outcome
|
||||
BatchRunOutcome outcome = command.run(runContext);
|
||||
|
||||
// Mark run as completed
|
||||
|
||||
@@ -25,5 +25,10 @@
|
||||
* AP-005: CLI adapter and complete M2 object graph wiring.
|
||||
* <p>
|
||||
* AP-006: Wires FilesystemRunLockPortAdapter (adapter-out) from validated config; retired temporary no-op lock.
|
||||
* <p>
|
||||
* AP-007: Adds SQLite persistence schema initialization at startup via
|
||||
* {@link de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitializationPort},
|
||||
* ensuring the database is ready before document processing begins. Schema initialization failure
|
||||
* is treated as a hard bootstrap error and causes exit code 1.
|
||||
*/
|
||||
package de.gecheckt.pdf.umbenenner.bootstrap;
|
||||
Reference in New Issue
Block a user