Implementierung für M2 vorläufig abgeschlossen
This commit is contained in:
@@ -5,11 +5,20 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
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.lock.FilesystemRunLockPortAdapter;
|
||||
import de.gecheckt.pdf.umbenenner.application.config.InvalidStartConfigurationException;
|
||||
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.RunBatchProcessingUseCase;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.usecase.NoOpRunBatchProcessingUseCase;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.usecase.M2BatchRunProcessingUseCase;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Manual bootstrap runner that constructs the object graph and drives the startup flow.
|
||||
@@ -17,7 +26,7 @@ import de.gecheckt.pdf.umbenenner.application.usecase.NoOpRunBatchProcessingUseC
|
||||
* AP-003 Implementation: Creates all required components using plain Java constructor injection
|
||||
* and executes the minimal no-op batch processing path.
|
||||
* <p>
|
||||
* AP-005: Integrates configuration loading via PropertiesConfigurationPortAdapter.
|
||||
* AP-005: CLI adapter and bootstrap wiring for M2 batch orchestration with run lock integration.
|
||||
* <p>
|
||||
* AP-006: Validates configuration before processing begins, returns exit code 1 on invalid config.
|
||||
*/
|
||||
@@ -26,6 +35,7 @@ public class BootstrapRunner {
|
||||
private static final Logger LOG = LogManager.getLogger(BootstrapRunner.class);
|
||||
|
||||
private final ConfigurationPortFactory configPortFactory;
|
||||
private final RunLockPortFactory runLockPortFactory;
|
||||
private final ValidatorFactory validatorFactory;
|
||||
private final UseCaseFactory useCaseFactory;
|
||||
private final CommandFactory commandFactory;
|
||||
@@ -38,6 +48,14 @@ public class BootstrapRunner {
|
||||
ConfigurationPort create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional interface for creating a RunLockPort from the configured lock file path.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RunLockPortFactory {
|
||||
RunLockPort create(Path lockFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional interface for creating a StartConfigurationValidator.
|
||||
*/
|
||||
@@ -51,7 +69,7 @@ public class BootstrapRunner {
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface UseCaseFactory {
|
||||
RunBatchProcessingUseCase create(ConfigurationPort configPort);
|
||||
RunBatchProcessingUseCase create(ConfigurationPort configPort, RunLockPort runLockPort);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,11 +82,14 @@ public class BootstrapRunner {
|
||||
|
||||
/**
|
||||
* Creates the BootstrapRunner with default factories for production use.
|
||||
* <p>
|
||||
* AP-006: Uses FilesystemRunLockPortAdapter for file-based exclusive run locking.
|
||||
*/
|
||||
public BootstrapRunner() {
|
||||
this.configPortFactory = PropertiesConfigurationPortAdapter::new;
|
||||
this.runLockPortFactory = FilesystemRunLockPortAdapter::new;
|
||||
this.validatorFactory = StartConfigurationValidator::new;
|
||||
this.useCaseFactory = NoOpRunBatchProcessingUseCase::new;
|
||||
this.useCaseFactory = M2BatchRunProcessingUseCase::new;
|
||||
this.commandFactory = SchedulerBatchCommand::new;
|
||||
}
|
||||
|
||||
@@ -76,15 +97,18 @@ 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 RunBatchProcessingUseCase instances
|
||||
* @param commandFactory factory for creating SchedulerBatchCommand instances
|
||||
*/
|
||||
public BootstrapRunner(ConfigurationPortFactory configPortFactory,
|
||||
RunLockPortFactory runLockPortFactory,
|
||||
ValidatorFactory validatorFactory,
|
||||
UseCaseFactory useCaseFactory,
|
||||
CommandFactory commandFactory) {
|
||||
this.configPortFactory = configPortFactory;
|
||||
this.runLockPortFactory = runLockPortFactory;
|
||||
this.validatorFactory = validatorFactory;
|
||||
this.useCaseFactory = useCaseFactory;
|
||||
this.commandFactory = commandFactory;
|
||||
@@ -112,20 +136,37 @@ public class BootstrapRunner {
|
||||
StartConfigurationValidator validator = validatorFactory.create();
|
||||
validator.validate(config);
|
||||
|
||||
// Step 4: Create the use case with the configuration port (application layer)
|
||||
RunBatchProcessingUseCase useCase = useCaseFactory.create(configPort);
|
||||
// Step 4: Create the run lock port from the validated config (AP-006)
|
||||
RunLockPort runLockPort = runLockPortFactory.create(config.runtimeLockFile());
|
||||
|
||||
// Step 5: Create the CLI command adapter with the use case
|
||||
// Step 5: Create the batch run context (M2-AP-003)
|
||||
// Generate a unique run ID and initialize the 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 configuration port and run lock (application layer)
|
||||
RunBatchProcessingUseCase useCase = useCaseFactory.create(configPort, runLockPort);
|
||||
|
||||
// Step 7: Create the CLI command adapter with the use case
|
||||
SchedulerBatchCommand command = commandFactory.create(useCase);
|
||||
|
||||
// Step 6: Execute the command
|
||||
boolean success = command.run();
|
||||
// Step 8: Execute the command with the run context and handle the outcome
|
||||
BatchRunOutcome outcome = command.run(runContext);
|
||||
|
||||
if (success) {
|
||||
LOG.info("No-op startup path completed successfully.");
|
||||
// Mark run as completed (AP-003)
|
||||
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 success ? 0 : 1;
|
||||
} catch (InvalidStartConfigurationException e) {
|
||||
// Controlled failure for invalid configuration - log clearly without stack trace
|
||||
LOG.error("Configuration validation failed: {}", e.getMessage());
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
/**
|
||||
* Bootstrap module for application startup and technical wiring.
|
||||
* Bootstrap module for application startup and technical object graph construction.
|
||||
* <p>
|
||||
* Responsibility: Orchestrate the startup flow, load configuration, validate it,
|
||||
* create and wire all application components, and invoke the CLI adapter entry point.
|
||||
* <p>
|
||||
* Components:
|
||||
* <ul>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.bootstrap.BootstrapRunner}
|
||||
* — Orchestrator of startup sequence and object graph construction (M2-AP-005)</li>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.bootstrap.PdfUmbenennerApplication}
|
||||
* — Main entry point that invokes BootstrapRunner (M1)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* M2 Implementation:
|
||||
* <ul>
|
||||
* <li>Uses factory pattern with pluggable interfaces for all ports and use cases</li>
|
||||
* <li>Manually constructs object graph without framework dependencies</li>
|
||||
* <li>Ensures strict inward dependency direction toward application and domain</li>
|
||||
* <li>Ready for extension by later milestones via factory methods</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* This package contains the main entry point and manual object graph construction.
|
||||
* AP-003: Provides a minimal, controlled startup path without dependency injection frameworks.
|
||||
* <p>
|
||||
* 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.
|
||||
*/
|
||||
package de.gecheckt.pdf.umbenenner.bootstrap;
|
||||
@@ -4,8 +4,11 @@ import de.gecheckt.pdf.umbenenner.adapter.inbound.cli.SchedulerBatchCommand;
|
||||
import de.gecheckt.pdf.umbenenner.application.config.InvalidStartConfigurationException;
|
||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
||||
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.RunBatchProcessingUseCase;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -35,8 +38,9 @@ class BootstrapRunnerTest {
|
||||
// Create mock factories that return working components
|
||||
BootstrapRunner runner = new BootstrapRunner(
|
||||
() -> mockConfigPort,
|
||||
lockFile -> new MockRunLockPort(),
|
||||
StartConfigurationValidator::new,
|
||||
port -> new MockRunBatchProcessingUseCase(true),
|
||||
(port, lock) -> new MockRunBatchProcessingUseCase(true),
|
||||
useCase -> new SchedulerBatchCommand(useCase)
|
||||
);
|
||||
|
||||
@@ -60,8 +64,9 @@ class BootstrapRunnerTest {
|
||||
|
||||
BootstrapRunner runner = new BootstrapRunner(
|
||||
() -> mockConfigPort,
|
||||
lockFile -> new MockRunLockPort(),
|
||||
() -> failingValidator,
|
||||
port -> new MockRunBatchProcessingUseCase(true),
|
||||
(port, lock) -> new MockRunBatchProcessingUseCase(true),
|
||||
useCase -> new SchedulerBatchCommand(useCase)
|
||||
);
|
||||
|
||||
@@ -79,8 +84,9 @@ class BootstrapRunnerTest {
|
||||
|
||||
BootstrapRunner runner = new BootstrapRunner(
|
||||
() -> failingConfigPort,
|
||||
lockFile -> new MockRunLockPort(),
|
||||
StartConfigurationValidator::new,
|
||||
port -> new MockRunBatchProcessingUseCase(true),
|
||||
(port, lock) -> new MockRunBatchProcessingUseCase(true),
|
||||
useCase -> new SchedulerBatchCommand(useCase)
|
||||
);
|
||||
|
||||
@@ -98,8 +104,9 @@ class BootstrapRunnerTest {
|
||||
|
||||
BootstrapRunner runner = new BootstrapRunner(
|
||||
() -> throwingConfigPort,
|
||||
lockFile -> new MockRunLockPort(),
|
||||
StartConfigurationValidator::new,
|
||||
port -> new MockRunBatchProcessingUseCase(true),
|
||||
(port, lock) -> new MockRunBatchProcessingUseCase(true),
|
||||
useCase -> new SchedulerBatchCommand(useCase)
|
||||
);
|
||||
|
||||
@@ -109,23 +116,45 @@ class BootstrapRunnerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void run_returnsOneWhenCommandReturnsFalse() throws Exception {
|
||||
void run_returnsOneWhenBatchFails() throws Exception {
|
||||
// Create a mock configuration port that returns valid config
|
||||
ConfigurationPort mockConfigPort = new MockConfigurationPort(tempDir, true);
|
||||
|
||||
// Create a use case that returns false
|
||||
RunBatchProcessingUseCase failingUseCase = () -> false;
|
||||
// Create a use case that returns failure outcome
|
||||
RunBatchProcessingUseCase failingUseCase = (context) -> BatchRunOutcome.FAILURE;
|
||||
|
||||
BootstrapRunner runner = new BootstrapRunner(
|
||||
() -> mockConfigPort,
|
||||
lockFile -> new MockRunLockPort(),
|
||||
StartConfigurationValidator::new,
|
||||
port -> failingUseCase,
|
||||
(port, lock) -> failingUseCase,
|
||||
useCase -> new SchedulerBatchCommand(useCase)
|
||||
);
|
||||
|
||||
int exitCode = runner.run();
|
||||
|
||||
assertEquals(1, exitCode, "Command returning false should return exit code 1");
|
||||
assertEquals(1, exitCode, "Batch failure outcome should return exit code 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void run_returnsOneWhenLockUnavailable() throws Exception {
|
||||
// AP-007: controlled early termination because another instance is already running
|
||||
// maps to exit code 1 (start protection counts as a hard startup failure)
|
||||
ConfigurationPort mockConfigPort = new MockConfigurationPort(tempDir, true);
|
||||
|
||||
RunBatchProcessingUseCase lockUnavailableUseCase = (context) -> BatchRunOutcome.LOCK_UNAVAILABLE;
|
||||
|
||||
BootstrapRunner runner = new BootstrapRunner(
|
||||
() -> mockConfigPort,
|
||||
lockFile -> new MockRunLockPort(),
|
||||
StartConfigurationValidator::new,
|
||||
(port, lock) -> lockUnavailableUseCase,
|
||||
useCase -> new SchedulerBatchCommand(useCase)
|
||||
);
|
||||
|
||||
int exitCode = runner.run();
|
||||
|
||||
assertEquals(1, exitCode, "Lock unavailable (another instance running) should return exit code 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -194,8 +223,23 @@ class BootstrapRunnerTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute() {
|
||||
return shouldSucceed;
|
||||
public BatchRunOutcome execute(BatchRunContext context) {
|
||||
return shouldSucceed ? BatchRunOutcome.SUCCESS : BatchRunOutcome.FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock RunLockPort for testing.
|
||||
*/
|
||||
private static class MockRunLockPort implements RunLockPort {
|
||||
@Override
|
||||
public void acquire() {
|
||||
// Mock: no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
// Mock: no-op
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user