1
0

M6 komplett umgesetzt

This commit is contained in:
2026-04-07 12:26:14 +02:00
parent 506f5ac32e
commit 8bcd80d70a
51 changed files with 5960 additions and 536 deletions

View File

@@ -9,31 +9,43 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.gecheckt.pdf.umbenenner.adapter.in.cli.SchedulerBatchCommand;
import de.gecheckt.pdf.umbenenner.adapter.out.ai.OpenAiHttpAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.bootstrap.validation.InvalidStartConfigurationException;
import de.gecheckt.pdf.umbenenner.adapter.out.bootstrap.validation.StartConfigurationValidator;
import de.gecheckt.pdf.umbenenner.adapter.out.clock.SystemClockAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.configuration.ConfigurationLoadingException;
import de.gecheckt.pdf.umbenenner.adapter.out.configuration.PropertiesConfigurationPortAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.fingerprint.Sha256FingerprintAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.lock.FilesystemRunLockPortAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.pdfextraction.PdfTextExtractionPortAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.prompt.FilesystemPromptPortAdapter;
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.adapter.out.targetcopy.FilesystemTargetFileCopyAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.targetfolder.FilesystemTargetFolderAdapter;
import de.gecheckt.pdf.umbenenner.application.config.RuntimeConfiguration;
import de.gecheckt.pdf.umbenenner.application.config.startup.StartConfiguration;
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome;
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProcessingUseCase;
import de.gecheckt.pdf.umbenenner.application.port.out.AiInvocationPort;
import de.gecheckt.pdf.umbenenner.application.port.out.ClockPort;
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileCopyPort;
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFolderPort;
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.ProcessingLogger;
import de.gecheckt.pdf.umbenenner.application.port.out.PromptPort;
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
import de.gecheckt.pdf.umbenenner.application.service.AiNamingService;
import de.gecheckt.pdf.umbenenner.application.service.AiResponseValidator;
import de.gecheckt.pdf.umbenenner.application.service.DocumentProcessingCoordinator;
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultBatchRunProcessingUseCase;
import de.gecheckt.pdf.umbenenner.bootstrap.adapter.Log4jProcessingLogger;
@@ -71,11 +83,15 @@ import de.gecheckt.pdf.umbenenner.domain.model.RunId;
* <ul>
* <li>{@link PropertiesConfigurationPortAdapter} — loads configuration from properties and environment.</li>
* <li>{@link FilesystemRunLockPortAdapter} — ensures exclusive execution via a lock file.</li>
* <li>{@link SqliteSchemaInitializationAdapter} — initializes SQLite schema at startup.</li>
* <li>{@link SqliteSchemaInitializationAdapter} — initializes SQLite schema (including target-copy
* schema evolution) at startup.</li>
* <li>{@link Sha256FingerprintAdapter} — provides content-based document identification.</li>
* <li>{@link SqliteDocumentRecordRepositoryAdapter} — manages document master records.</li>
* <li>{@link SqliteProcessingAttemptRepositoryAdapter} — maintains attempt history.</li>
* <li>{@link SqliteUnitOfWorkAdapter} — coordinates atomic persistence operations.</li>
* <li>{@link FilesystemTargetFolderAdapter} — resolves unique filenames in the configured target folder.</li>
* <li>{@link FilesystemTargetFileCopyAdapter} — copies source documents to the target folder via
* a temporary file and final move/rename.</li>
* </ul>
* <p>
* Schema initialization is performed exactly once in {@link #run()} before the batch processing loop
@@ -162,12 +178,22 @@ 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.</li>
* <li>{@link SqliteSchemaInitializationAdapter} for SQLite schema DDL and target-copy schema
* evolution at startup.</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>
* <li>{@link FilesystemTargetFolderAdapter} for duplicate-safe filename resolution in the
* configured {@code target.folder}.</li>
* <li>{@link FilesystemTargetFileCopyAdapter} for copying source documents to the target folder
* via a temporary file and final atomic move/rename.</li>
* </ul>
* <p>
* Target folder availability and write access are validated in
* {@link #loadAndValidateConfiguration()} via {@link StartConfigurationValidator} before
* schema initialisation and batch processing begin. If the target folder does not yet exist,
* the validator creates it; failure to do so is a hard startup error.
* <p>
* Schema initialisation is performed explicitly in {@link #run()} before the batch loop
* begins. Failure during initialisation aborts the run with exit code 1.
*/
@@ -189,8 +215,23 @@ public class BootstrapRunner {
UnitOfWorkPort unitOfWorkPort =
new SqliteUnitOfWorkAdapter(jdbcUrl);
ProcessingLogger coordinatorLogger = new Log4jProcessingLogger(DocumentProcessingCoordinator.class);
TargetFolderPort targetFolderPort = new FilesystemTargetFolderAdapter(startConfig.targetFolder());
TargetFileCopyPort targetFileCopyPort = new FilesystemTargetFileCopyAdapter(startConfig.targetFolder());
DocumentProcessingCoordinator documentProcessingCoordinator =
new DocumentProcessingCoordinator(documentRecordRepository, processingAttemptRepository, unitOfWorkPort, coordinatorLogger);
new DocumentProcessingCoordinator(documentRecordRepository, processingAttemptRepository, unitOfWorkPort, targetFolderPort, targetFileCopyPort, coordinatorLogger);
// Wire AI naming pipeline
AiInvocationPort aiInvocationPort = new OpenAiHttpAdapter(startConfig);
PromptPort promptPort = new FilesystemPromptPortAdapter(startConfig.promptTemplateFile());
ClockPort clockPort = new SystemClockAdapter();
AiResponseValidator aiResponseValidator = new AiResponseValidator(clockPort);
AiNamingService aiNamingService = new AiNamingService(
aiInvocationPort,
promptPort,
aiResponseValidator,
startConfig.apiModel(),
startConfig.maxTextCharacters());
ProcessingLogger useCaseLogger = new Log4jProcessingLogger(DefaultBatchRunProcessingUseCase.class);
return new DefaultBatchRunProcessingUseCase(
runtimeConfig,
@@ -199,6 +240,7 @@ public class BootstrapRunner {
new PdfTextExtractionPortAdapter(),
fingerprintPort,
documentProcessingCoordinator,
aiNamingService,
useCaseLogger);
};
this.commandFactory = SchedulerBatchCommand::new;
@@ -272,8 +314,17 @@ 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.
* {@link StartConfigurationValidator}.
* <p>
* Validation includes:
* <ul>
* <li>{@code source.folder}: must exist, be a directory, and be readable.</li>
* <li>{@code target.folder}: must exist as a writable directory, or be technically
* creatable (validator attempts {@code Files.createDirectories} if absent;
* failure here is a hard startup error).</li>
* <li>{@code sqlite.file}: parent directory must exist.</li>
* <li>All numeric and URI constraints.</li>
* </ul>
*/
private StartConfiguration loadAndValidateConfiguration() {
ConfigurationPort configPort = configPortFactory.create();

View File

@@ -28,11 +28,15 @@
* Startup sequence:
* <ul>
* <li>Load and validate complete startup configuration from properties file and environment variables</li>
* <li>Initialize SQLite persistence schema via {@link de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitializationPort},
* <li>Validate target folder availability and write access; create target folder if absent
* (failure is a hard startup error)</li>
* <li>Initialize SQLite persistence schema (including target-copy schema evolution) via
* {@link de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitializationPort},
* ensuring the database is ready before any batch processing</li>
* <li>Schema initialization failure is treated as a hard bootstrap error and causes exit code 1</li>
* <li>Create run lock adapter and acquire exclusive lock</li>
* <li>Wire all outbound adapters (document candidates, PDF extraction, fingerprint, persistence, logging)</li>
* <li>Wire all outbound adapters (document candidates, PDF extraction, fingerprint, persistence,
* target folder duplicate resolution, target file copy, logging)</li>
* <li>Wire and invoke the batch processing CLI adapter</li>
* <li>Map batch outcome to process exit code</li>
* </ul>