1
0

Optimierung: BootstrapRunner strukturell verschlankt

This commit is contained in:
2026-04-04 13:15:46 +02:00
parent 3ab10a89f0
commit efb4d0b222

View File

@@ -212,20 +212,15 @@ public class BootstrapRunner {
* <p>
* Startup flow:
* <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>Load and validate the configuration.</li>
* <li>Initialise the SQLite persistence schema. 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 required 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
* 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.
*