M3-AP-007 Bootstrap- und CLI-Anpassungen Pre-Version
This commit is contained in:
@@ -70,6 +70,8 @@ public class StartConfigurationValidator {
|
|||||||
errors.add("- source.folder: path does not exist: " + sourceFolder);
|
errors.add("- source.folder: path does not exist: " + sourceFolder);
|
||||||
} else if (!Files.isDirectory(sourceFolder)) {
|
} else if (!Files.isDirectory(sourceFolder)) {
|
||||||
errors.add("- source.folder: path is not a directory: " + sourceFolder);
|
errors.add("- source.folder: path is not a directory: " + sourceFolder);
|
||||||
|
} else if (!Files.isReadable(sourceFolder)) {
|
||||||
|
errors.add("- source.folder: directory is not readable: " + sourceFolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import de.gecheckt.pdf.umbenenner.adapter.inbound.cli.SchedulerBatchCommand;
|
import de.gecheckt.pdf.umbenenner.adapter.inbound.cli.SchedulerBatchCommand;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.outbound.configuration.PropertiesConfigurationPortAdapter;
|
import de.gecheckt.pdf.umbenenner.adapter.outbound.configuration.PropertiesConfigurationPortAdapter;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.outbound.lock.FilesystemRunLockPortAdapter;
|
import de.gecheckt.pdf.umbenenner.adapter.outbound.lock.FilesystemRunLockPortAdapter;
|
||||||
|
import de.gecheckt.pdf.umbenenner.adapter.outbound.pdfextraction.PdfTextExtractionPortAdapter;
|
||||||
|
import de.gecheckt.pdf.umbenenner.adapter.outbound.sourcedocument.SourceDocumentCandidatesPortAdapter;
|
||||||
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;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome;
|
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.RunBatchProcessingUseCase;
|
import de.gecheckt.pdf.umbenenner.application.port.in.RunBatchProcessingUseCase;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.PdfTextExtractionPort;
|
|
||||||
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.SourceDocumentCandidatesPort;
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.usecase.M2BatchRunProcessingUseCase;
|
import de.gecheckt.pdf.umbenenner.application.usecase.M2BatchRunProcessingUseCase;
|
||||||
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;
|
||||||
@@ -27,12 +27,22 @@ 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>
|
||||||
* AP-003 Implementation: Creates all required components using plain Java constructor injection
|
* Responsibilities:
|
||||||
* and executes the minimal no-op batch processing path.
|
* <ol>
|
||||||
|
* <li>Load and validate the startup configuration</li>
|
||||||
|
* <li>Resolve the run-lock file path (with default fallback)</li>
|
||||||
|
* <li>Create and wire all ports and adapters</li>
|
||||||
|
* <li>Start the CLI adapter and execute the batch use case</li>
|
||||||
|
* <li>Map the batch outcome to a process exit code</li>
|
||||||
|
* </ol>
|
||||||
* <p>
|
* <p>
|
||||||
* AP-005: CLI adapter and bootstrap wiring for M2 batch orchestration with run lock integration.
|
* Exit code semantics (M3):
|
||||||
* <p>
|
* <ul>
|
||||||
* AP-006: Validates configuration before processing begins, returns exit code 1 on invalid config.
|
* <li>{@code 0}: Batch run executed successfully; individual document failures do not
|
||||||
|
* change the exit code as long as the run itself completed without a hard infrastructure error.</li>
|
||||||
|
* <li>{@code 1}: Hard start, bootstrap, or configuration failure that prevented the run
|
||||||
|
* from beginning, or a critical infrastructure failure during the run.</li>
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class BootstrapRunner {
|
public class BootstrapRunner {
|
||||||
|
|
||||||
@@ -72,10 +82,8 @@ public class BootstrapRunner {
|
|||||||
* Functional interface for creating a RunBatchProcessingUseCase.
|
* Functional interface for creating a RunBatchProcessingUseCase.
|
||||||
* <p>
|
* <p>
|
||||||
* Receives the already-loaded and validated {@link StartConfiguration} and run lock port.
|
* Receives the already-loaded and validated {@link StartConfiguration} and run lock port.
|
||||||
* <p>
|
* The factory is responsible for creating and wiring any additional outbound ports
|
||||||
* Note: The use case signature may accept additional ports for M3+ functionality,
|
* required by the use case (e.g., source document port, PDF extraction port).
|
||||||
* but bootstrap provides No-Op implementations for now (AP-005 scope).
|
|
||||||
* Full M3 adapter wiring will be completed in AP-007 (Bootstrap expansion).
|
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface UseCaseFactory {
|
public interface UseCaseFactory {
|
||||||
@@ -93,16 +101,23 @@ public class BootstrapRunner {
|
|||||||
/**
|
/**
|
||||||
* Creates the BootstrapRunner with default factories for production use.
|
* Creates the BootstrapRunner with default factories for production use.
|
||||||
* <p>
|
* <p>
|
||||||
* AP-006: Uses FilesystemRunLockPortAdapter for file-based exclusive run locking.
|
* Wires the full M3 processing pipeline:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link PropertiesConfigurationPortAdapter} for configuration loading</li>
|
||||||
|
* <li>{@link FilesystemRunLockPortAdapter} for exclusive run locking</li>
|
||||||
|
* <li>{@link SourceDocumentCandidatesPortAdapter} for PDF candidate discovery</li>
|
||||||
|
* <li>{@link PdfTextExtractionPortAdapter} for PDFBox-based text and page count extraction</li>
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public BootstrapRunner() {
|
public BootstrapRunner() {
|
||||||
this.configPortFactory = PropertiesConfigurationPortAdapter::new;
|
this.configPortFactory = PropertiesConfigurationPortAdapter::new;
|
||||||
this.runLockPortFactory = FilesystemRunLockPortAdapter::new;
|
this.runLockPortFactory = FilesystemRunLockPortAdapter::new;
|
||||||
this.validatorFactory = StartConfigurationValidator::new;
|
this.validatorFactory = StartConfigurationValidator::new;
|
||||||
// AP-005: Use case accepts M3 ports, but bootstrap provides No-Op implementations (M2 scope)
|
this.useCaseFactory = (config, lock) -> new M2BatchRunProcessingUseCase(
|
||||||
// AP-007 will wire real M3 adapters; for now, M2 uses No-Op ports
|
config,
|
||||||
this.useCaseFactory = (config, lock) ->
|
lock,
|
||||||
new M2BatchRunProcessingUseCase(config, lock, new NoOpSourceCandidatesPort(), new NoOpExtractionPort());
|
new SourceDocumentCandidatesPortAdapter(config.sourceFolder()),
|
||||||
|
new PdfTextExtractionPortAdapter());
|
||||||
this.commandFactory = SchedulerBatchCommand::new;
|
this.commandFactory = SchedulerBatchCommand::new;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,10 +178,9 @@ public class BootstrapRunner {
|
|||||||
BatchRunContext runContext = new BatchRunContext(runId, Instant.now());
|
BatchRunContext runContext = new BatchRunContext(runId, Instant.now());
|
||||||
LOG.info("Batch run started. RunId: {}", runId);
|
LOG.info("Batch run started. RunId: {}", runId);
|
||||||
|
|
||||||
// Step 6: Create the use case with the validated config and run lock (application layer)
|
// Step 6: Create the use case with the validated config and run lock (application layer).
|
||||||
// Config is passed directly; the use case does not re-read the properties file.
|
// Config is passed directly; the use case does not re-read the properties file.
|
||||||
// Note: The use case signature includes M3 ports, but bootstrap (M2 scope) provides No-Op implementations.
|
// M3 adapters (source document port, PDF extraction port) are wired by the factory.
|
||||||
// Real M3 adapter wiring will be completed in AP-007.
|
|
||||||
RunBatchProcessingUseCase useCase = useCaseFactory.create(config, runLockPort);
|
RunBatchProcessingUseCase useCase = useCaseFactory.create(config, runLockPort);
|
||||||
|
|
||||||
// Step 7: Create the CLI command adapter with the use case
|
// Step 7: Create the CLI command adapter with the use case
|
||||||
@@ -203,35 +217,4 @@ public class BootstrapRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// AP-005 (M2 scope): No-Op port implementations
|
|
||||||
// (Real M3 adapters will be wired in AP-007)
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No-Op implementation of {@link SourceDocumentCandidatesPort} for M2 scope.
|
|
||||||
* <p>
|
|
||||||
* M2 batch execution does not scan the source folder, so this returns an empty list.
|
|
||||||
* AP-007 will replace this with a real filesystem adapter.
|
|
||||||
*/
|
|
||||||
private static class NoOpSourceCandidatesPort implements SourceDocumentCandidatesPort {
|
|
||||||
@Override
|
|
||||||
public java.util.List<de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate> loadCandidates() {
|
|
||||||
return java.util.List.of();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No-Op implementation of {@link PdfTextExtractionPort} for M2 scope.
|
|
||||||
* <p>
|
|
||||||
* M2 batch execution does not extract PDF content, so this port is never called.
|
|
||||||
* AP-007 will replace this with a real PDFBox adapter.
|
|
||||||
*/
|
|
||||||
private static class NoOpExtractionPort implements PdfTextExtractionPort {
|
|
||||||
@Override
|
|
||||||
public de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult extractTextAndPageCount(
|
|
||||||
de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate candidate) {
|
|
||||||
throw new UnsupportedOperationException("M2 scope: No-Op port, should not be called");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -203,6 +203,30 @@ class BootstrapRunnerTest {
|
|||||||
"Lock path passed to adapter must not be blank (default must be applied)");
|
"Lock path passed to adapter must not be blank (default must be applied)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* M3 exit-code contract: a completed batch run returns exit code 0 even when individual
|
||||||
|
* documents ended with M3 content errors or technical document errors.
|
||||||
|
* Only hard infrastructure or startup failures must cause exit code 1.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void run_returnsZeroWhenBatchRunCompletesWithDocumentLevelFailures() throws Exception {
|
||||||
|
ConfigurationPort mockConfigPort = new MockConfigurationPort(tempDir, true);
|
||||||
|
// BatchRunOutcome.SUCCESS is what the M3 use case returns when the run completed,
|
||||||
|
// regardless of how many individual documents had content or technical errors.
|
||||||
|
RunBatchProcessingUseCase useCaseWithDocumentFailures = (context) -> BatchRunOutcome.SUCCESS;
|
||||||
|
|
||||||
|
BootstrapRunner runner = new BootstrapRunner(
|
||||||
|
() -> mockConfigPort,
|
||||||
|
lockFile -> new MockRunLockPort(),
|
||||||
|
StartConfigurationValidator::new,
|
||||||
|
(config, lock) -> useCaseWithDocumentFailures,
|
||||||
|
SchedulerBatchCommand::new
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(0, runner.run(),
|
||||||
|
"M3 contract: batch completion with document-level failures must return exit code 0");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void run_withDefaultConstructor_usesRealImplementations() {
|
void run_withDefaultConstructor_usesRealImplementations() {
|
||||||
BootstrapRunner runner = new BootstrapRunner();
|
BootstrapRunner runner = new BootstrapRunner();
|
||||||
|
|||||||
Reference in New Issue
Block a user