diff --git a/pdf-umbenenner-bootstrap/src/main/java/de/gecheckt/pdf/umbenenner/bootstrap/BootstrapRunner.java b/pdf-umbenenner-bootstrap/src/main/java/de/gecheckt/pdf/umbenenner/bootstrap/BootstrapRunner.java index 48771ec..a39c799 100644 --- a/pdf-umbenenner-bootstrap/src/main/java/de/gecheckt/pdf/umbenenner/bootstrap/BootstrapRunner.java +++ b/pdf-umbenenner-bootstrap/src/main/java/de/gecheckt/pdf/umbenenner/bootstrap/BootstrapRunner.java @@ -212,20 +212,15 @@ public class BootstrapRunner { *
* Startup flow: *
- * Document-level failures during the batch loop (step 6) are not startup failures and + * Document-level failures during the batch loop are not startup failures and * do not change the exit code as long as the run itself completes without a hard * infrastructure error. * @@ -235,72 +230,22 @@ public class BootstrapRunner { public int run() { LOG.info("Bootstrap flow started."); try { - // Step 1: Create the configuration port adapter (adapter-out layer) - ConfigurationPort configPort = configPortFactory.create(); - - // Step 2: Load configuration - var config = configPort.loadConfiguration(); - - // Step 3: Validate configuration. - // Includes checking that sqlite.file parent directory exists or is creatable. - StartConfigurationValidator validator = validatorFactory.create(); - validator.validate(config); - - // 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"); - LOG.info("runtime.lock.file not configured, using default lock path: {}", - lockFilePath.toAbsolutePath()); - } - RunLockPort runLockPort = runLockPortFactory.create(lockFilePath); - - // 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 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. + StartConfiguration config = loadAndValidateConfiguration(); + initializeSchema(config); + RunLockPort runLockPort = runLockPortFactory.create(resolveLockFilePath(config)); + BatchRunContext runContext = createRunContext(); BatchRunProcessingUseCase useCase = useCaseFactory.create(config, runLockPort); - - // Step 8: Create the CLI command adapter with the use case SchedulerBatchCommand command = commandFactory.create(useCase); - - // Step 9: Execute the command with the run context and handle the outcome BatchRunOutcome outcome = command.run(runContext); - - // Mark run as completed runContext.setEndInstant(Instant.now()); - - if (outcome.isSuccess()) { - LOG.info("Batch run completed successfully. RunId: {}", runContext.runId()); - return 0; - } else if (outcome.isLockUnavailable()) { - LOG.warn("Batch run aborted: another instance is already running. RunId: {}", - runContext.runId()); - return 1; - } else { - LOG.error("Batch run failed. RunId: {}", runContext.runId()); - return 1; - } + return mapOutcomeToExitCode(outcome, runContext); } catch (InvalidStartConfigurationException e) { - // Controlled failure for invalid configuration – log clearly without stack trace LOG.error("Configuration validation failed: {}", e.getMessage()); return 1; } catch (IllegalStateException e) { - // Configuration loading failed due to missing/invalid required properties LOG.error("Configuration loading failed: {}", e.getMessage()); return 1; } catch (DocumentPersistenceException e) { - // Persistence operation failed – hard error LOG.error("Persistence operation failed: {}", e.getMessage(), e); return 1; } catch (Exception e) { @@ -309,6 +254,67 @@ public class BootstrapRunner { } } + /** + * Loads configuration via {@link ConfigurationPort} and validates it via + * {@link StartConfigurationValidator}. Validation includes checking that the + * {@code sqlite.file} parent directory exists or is technically creatable. + */ + private StartConfiguration loadAndValidateConfiguration() { + ConfigurationPort configPort = configPortFactory.create(); + StartConfiguration config = configPort.loadConfiguration(); + validatorFactory.create().validate(config); + return config; + } + + /** + * Initialises the SQLite persistence schema once at startup, before the batch loop begins. + * Failure here is a hard bootstrap error and results in exit code 1. + */ + private void initializeSchema(StartConfiguration config) { + schemaInitPortFactory.create(buildJdbcUrl(config)).initializeSchema(); + } + + /** + * Resolves the run-lock file path from the configuration, applying a default when not set. + */ + private Path resolveLockFilePath(StartConfiguration config) { + Path lockFilePath = config.runtimeLockFile(); + if (lockFilePath == null || lockFilePath.toString().isBlank()) { + lockFilePath = Paths.get("pdf-umbenenner.lock"); + LOG.info("runtime.lock.file not configured, using default lock path: {}", + lockFilePath.toAbsolutePath()); + } + return lockFilePath; + } + + /** + * Creates a new {@link BatchRunContext} with a fresh run ID and the current timestamp. + */ + private BatchRunContext createRunContext() { + RunId runId = new RunId(UUID.randomUUID().toString()); + LOG.info("Batch run started. RunId: {}", runId); + return new BatchRunContext(runId, Instant.now()); + } + + /** + * Maps a {@link BatchRunOutcome} to a process exit code and logs the run result. + * + * @return 0 if the batch run completed successfully; 1 otherwise + */ + private int mapOutcomeToExitCode(BatchRunOutcome outcome, BatchRunContext runContext) { + if (outcome.isSuccess()) { + LOG.info("Batch run completed successfully. RunId: {}", runContext.runId()); + return 0; + } else if (outcome.isLockUnavailable()) { + LOG.warn("Batch run aborted: another instance is already running. RunId: {}", + runContext.runId()); + return 1; + } else { + LOG.error("Batch run failed. RunId: {}", runContext.runId()); + return 1; + } + } + /** * Builds the JDBC URL for the SQLite database from the configured file path. *