Nachbearbeitung: Logging aus der Application-Schicht entkoppelt
This commit is contained in:
@@ -35,6 +35,10 @@
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
|
||||
@@ -19,12 +19,6 @@
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.port.out;
|
||||
|
||||
/**
|
||||
* Outbound port for processing-related logging operations.
|
||||
* <p>
|
||||
* The application delegates all logging to this port to remain decoupled from
|
||||
* specific logging frameworks. Concrete implementations are provided by adapters.
|
||||
*/
|
||||
public interface ProcessingLogger {
|
||||
|
||||
/**
|
||||
* Logs an information-level message.
|
||||
*
|
||||
* @param message the message template (may contain {} placeholders)
|
||||
* @param args optional message arguments for placeholder substitution
|
||||
*/
|
||||
void info(String message, Object... args);
|
||||
|
||||
/**
|
||||
* Logs a debug-level message.
|
||||
*
|
||||
* @param message the message template (may contain {} placeholders)
|
||||
* @param args optional message arguments for placeholder substitution
|
||||
*/
|
||||
void debug(String message, Object... args);
|
||||
|
||||
/**
|
||||
* Logs a warning-level message.
|
||||
*
|
||||
* @param message the message template (may contain {} placeholders)
|
||||
* @param args optional message arguments for placeholder substitution
|
||||
*/
|
||||
void warn(String message, Object... args);
|
||||
|
||||
/**
|
||||
* Logs an error-level message with optional arguments and exception.
|
||||
* <p>
|
||||
* If the last argument is a Throwable, it is treated as the exception to log.
|
||||
* Otherwise, all arguments are treated as message parameters.
|
||||
*
|
||||
* @param message the message template (may contain {} placeholders)
|
||||
* @param args optional message arguments and/or exception
|
||||
*/
|
||||
void error(String message, Object... args);
|
||||
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.FailureCounters;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceLookupTechnicalFailure;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
|
||||
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.UnitOfWorkPort;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||
@@ -22,9 +23,6 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
@@ -80,14 +78,13 @@ import java.util.function.Function;
|
||||
*/
|
||||
public class DocumentProcessingCoordinator {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(DocumentProcessingCoordinator.class);
|
||||
|
||||
private final DocumentRecordRepository documentRecordRepository;
|
||||
private final ProcessingAttemptRepository processingAttemptRepository;
|
||||
private final UnitOfWorkPort unitOfWorkPort;
|
||||
private final ProcessingLogger logger;
|
||||
|
||||
/**
|
||||
* Creates the document processor with the required persistence ports.
|
||||
* Creates the document processor with the required persistence ports and logger.
|
||||
*
|
||||
* @param documentRecordRepository port for reading and writing the document master record;
|
||||
* must not be null
|
||||
@@ -95,18 +92,21 @@ public class DocumentProcessingCoordinator {
|
||||
* must not be null
|
||||
* @param unitOfWorkPort port for executing operations atomically;
|
||||
* must not be null
|
||||
* @param logger for processing-related logging; must not be null
|
||||
* @throws NullPointerException if any parameter is null
|
||||
*/
|
||||
public DocumentProcessingCoordinator(
|
||||
DocumentRecordRepository documentRecordRepository,
|
||||
ProcessingAttemptRepository processingAttemptRepository,
|
||||
UnitOfWorkPort unitOfWorkPort) {
|
||||
UnitOfWorkPort unitOfWorkPort,
|
||||
ProcessingLogger logger) {
|
||||
this.documentRecordRepository =
|
||||
Objects.requireNonNull(documentRecordRepository, "documentRecordRepository must not be null");
|
||||
this.processingAttemptRepository =
|
||||
Objects.requireNonNull(processingAttemptRepository, "processingAttemptRepository must not be null");
|
||||
this.unitOfWorkPort =
|
||||
Objects.requireNonNull(unitOfWorkPort, "unitOfWorkPort must not be null");
|
||||
this.logger = Objects.requireNonNull(logger, "logger must not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,7 +192,7 @@ public class DocumentProcessingCoordinator {
|
||||
|
||||
// Step 2: Handle persistence lookup failure – cannot safely proceed
|
||||
if (lookupResult instanceof PersistenceLookupTechnicalFailure failure) {
|
||||
LOG.error("Cannot process '{}': master record lookup failed: {}",
|
||||
logger.error("Cannot process '{}': master record lookup failed: {}",
|
||||
candidate.uniqueIdentifier(), failure.errorMessage());
|
||||
return;
|
||||
}
|
||||
@@ -201,7 +201,7 @@ public class DocumentProcessingCoordinator {
|
||||
switch (lookupResult) {
|
||||
case DocumentTerminalSuccess terminalSuccess -> {
|
||||
// Document already successfully processed → skip
|
||||
LOG.info("Skipping '{}': already successfully processed (fingerprint: {}).",
|
||||
logger.info("Skipping '{}': already successfully processed (fingerprint: {}).",
|
||||
candidate.uniqueIdentifier(), fingerprint.sha256Hex());
|
||||
persistSkipAttempt(
|
||||
candidate, fingerprint, terminalSuccess.record(),
|
||||
@@ -211,7 +211,7 @@ public class DocumentProcessingCoordinator {
|
||||
|
||||
case DocumentTerminalFinalFailure terminalFailure -> {
|
||||
// Document finally failed → skip
|
||||
LOG.info("Skipping '{}': already finally failed (fingerprint: {}).",
|
||||
logger.info("Skipping '{}': already finally failed (fingerprint: {}).",
|
||||
candidate.uniqueIdentifier(), fingerprint.sha256Hex());
|
||||
persistSkipAttempt(
|
||||
candidate, fingerprint, terminalFailure.record(),
|
||||
@@ -235,7 +235,7 @@ public class DocumentProcessingCoordinator {
|
||||
|
||||
default ->
|
||||
// Exhaustive sealed hierarchy; this branch is unreachable
|
||||
LOG.error("Unexpected lookup result type for '{}': {}",
|
||||
logger.error("Unexpected lookup result type for '{}': {}",
|
||||
candidate.uniqueIdentifier(), lookupResult.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
@@ -291,11 +291,11 @@ public class DocumentProcessingCoordinator {
|
||||
txOps.updateDocumentRecord(skipRecord);
|
||||
});
|
||||
|
||||
LOG.debug("Skip attempt #{} persisted for '{}' with status {}.",
|
||||
logger.debug("Skip attempt #{} persisted for '{}' with status {}.",
|
||||
attemptNumber, candidate.uniqueIdentifier(), skipStatus);
|
||||
|
||||
} catch (DocumentPersistenceException e) {
|
||||
LOG.error("Failed to persist skip attempt for '{}': {}",
|
||||
logger.error("Failed to persist skip attempt for '{}': {}",
|
||||
candidate.uniqueIdentifier(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
@@ -514,14 +514,14 @@ public class DocumentProcessingCoordinator {
|
||||
recordWriter.accept(txOps);
|
||||
});
|
||||
|
||||
LOG.info("Document '{}' processed: status={}, contentErrors={}, transientErrors={}.",
|
||||
logger.info("Document '{}' processed: status={}, contentErrors={}, transientErrors={}.",
|
||||
candidate.uniqueIdentifier(),
|
||||
outcome.overallStatus(),
|
||||
outcome.counters().contentErrorCount(),
|
||||
outcome.counters().transientErrorCount());
|
||||
|
||||
} catch (DocumentPersistenceException e) {
|
||||
LOG.error("Failed to persist processing result for '{}': {}",
|
||||
logger.error("Failed to persist processing result for '{}': {}",
|
||||
candidate.uniqueIdentifier(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintResult;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintSuccess;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintTechnicalError;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.PdfTextExtractionPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingLogger;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockUnavailableException;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentAccessException;
|
||||
@@ -20,9 +21,6 @@ import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -79,14 +77,13 @@ import java.util.Objects;
|
||||
*/
|
||||
public class DefaultBatchRunProcessingUseCase implements BatchRunProcessingUseCase {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(DefaultBatchRunProcessingUseCase.class);
|
||||
|
||||
private final StartConfiguration configuration;
|
||||
private final RunLockPort runLockPort;
|
||||
private final SourceDocumentCandidatesPort sourceDocumentCandidatesPort;
|
||||
private final PdfTextExtractionPort pdfTextExtractionPort;
|
||||
private final FingerprintPort fingerprintPort;
|
||||
private final DocumentProcessingCoordinator documentProcessingCoordinator;
|
||||
private final ProcessingLogger logger;
|
||||
|
||||
/**
|
||||
* Creates the batch use case with the already-loaded startup configuration and all
|
||||
@@ -105,6 +102,7 @@ public class DefaultBatchRunProcessingUseCase implements BatchRunProcessingUseCa
|
||||
* must not be null
|
||||
* @param documentProcessingCoordinator for applying decision logic and persisting results;
|
||||
* must not be null
|
||||
* @param logger for processing-related logging; must not be null
|
||||
* @throws NullPointerException if any parameter is null
|
||||
*/
|
||||
public DefaultBatchRunProcessingUseCase(
|
||||
@@ -113,7 +111,8 @@ public class DefaultBatchRunProcessingUseCase implements BatchRunProcessingUseCa
|
||||
SourceDocumentCandidatesPort sourceDocumentCandidatesPort,
|
||||
PdfTextExtractionPort pdfTextExtractionPort,
|
||||
FingerprintPort fingerprintPort,
|
||||
DocumentProcessingCoordinator documentProcessingCoordinator) {
|
||||
DocumentProcessingCoordinator documentProcessingCoordinator,
|
||||
ProcessingLogger logger) {
|
||||
this.configuration = Objects.requireNonNull(configuration, "configuration must not be null");
|
||||
this.runLockPort = Objects.requireNonNull(runLockPort, "runLockPort must not be null");
|
||||
this.sourceDocumentCandidatesPort = Objects.requireNonNull(
|
||||
@@ -123,11 +122,12 @@ public class DefaultBatchRunProcessingUseCase implements BatchRunProcessingUseCa
|
||||
this.fingerprintPort = Objects.requireNonNull(fingerprintPort, "fingerprintPort must not be null");
|
||||
this.documentProcessingCoordinator = Objects.requireNonNull(
|
||||
documentProcessingCoordinator, "documentProcessingCoordinator must not be null");
|
||||
this.logger = Objects.requireNonNull(logger, "logger must not be null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchRunOutcome execute(BatchRunContext context) {
|
||||
LOG.info("Batch processing initiated. RunId: {}", context.runId());
|
||||
logger.info("Batch processing initiated. RunId: {}", context.runId());
|
||||
boolean lockAcquired = false;
|
||||
|
||||
try {
|
||||
@@ -135,23 +135,23 @@ public class DefaultBatchRunProcessingUseCase implements BatchRunProcessingUseCa
|
||||
try {
|
||||
runLockPort.acquire();
|
||||
lockAcquired = true;
|
||||
LOG.debug("Run lock acquired successfully.");
|
||||
logger.debug("Run lock acquired successfully.");
|
||||
} catch (RunLockUnavailableException e) {
|
||||
LOG.warn("Run lock not available – another instance is already running. "
|
||||
logger.warn("Run lock not available – another instance is already running. "
|
||||
+ "This instance terminates immediately.");
|
||||
return BatchRunOutcome.LOCK_UNAVAILABLE;
|
||||
}
|
||||
|
||||
LOG.debug("Configuration in use: source={}, target={}",
|
||||
logger.debug("Configuration in use: source={}, target={}",
|
||||
configuration.sourceFolder(), configuration.targetFolder());
|
||||
LOG.info("Batch run started. RunId: {}, Start: {}",
|
||||
logger.info("Batch run started. RunId: {}, Start: {}",
|
||||
context.runId(), context.startInstant());
|
||||
|
||||
// Load and process all candidates
|
||||
return processCandidates(context);
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.error("Unexpected error during batch processing", e);
|
||||
logger.error("Unexpected error during batch processing", e);
|
||||
return BatchRunOutcome.FAILURE;
|
||||
} finally {
|
||||
releaseLockIfAcquired(lockAcquired);
|
||||
@@ -169,17 +169,17 @@ public class DefaultBatchRunProcessingUseCase implements BatchRunProcessingUseCa
|
||||
try {
|
||||
candidates = sourceDocumentCandidatesPort.loadCandidates();
|
||||
} catch (SourceDocumentAccessException e) {
|
||||
LOG.error("Cannot access source folder: {}", e.getMessage(), e);
|
||||
logger.error("Cannot access source folder: {}", e.getMessage(), e);
|
||||
return BatchRunOutcome.FAILURE;
|
||||
}
|
||||
LOG.info("Found {} PDF candidate(s) in source folder.", candidates.size());
|
||||
logger.info("Found {} PDF candidate(s) in source folder.", candidates.size());
|
||||
|
||||
// Process each candidate
|
||||
for (SourceDocumentCandidate candidate : candidates) {
|
||||
processCandidate(candidate, context);
|
||||
}
|
||||
|
||||
LOG.info("Batch run completed. Processed {} candidate(s). RunId: {}",
|
||||
logger.info("Batch run completed. Processed {} candidate(s). RunId: {}",
|
||||
candidates.size(), context.runId());
|
||||
return BatchRunOutcome.SUCCESS;
|
||||
}
|
||||
@@ -196,9 +196,9 @@ public class DefaultBatchRunProcessingUseCase implements BatchRunProcessingUseCa
|
||||
if (lockAcquired) {
|
||||
try {
|
||||
runLockPort.release();
|
||||
LOG.debug("Run lock released.");
|
||||
logger.debug("Run lock released.");
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Warning: Failed to release run lock.", e);
|
||||
logger.warn("Warning: Failed to release run lock.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,7 +230,7 @@ public class DefaultBatchRunProcessingUseCase implements BatchRunProcessingUseCa
|
||||
* @param context the current batch run context
|
||||
*/
|
||||
private void processCandidate(SourceDocumentCandidate candidate, BatchRunContext context) {
|
||||
LOG.debug("Processing candidate: {}", candidate.uniqueIdentifier());
|
||||
logger.debug("Processing candidate: {}", candidate.uniqueIdentifier());
|
||||
|
||||
Instant attemptStart = Instant.now();
|
||||
FingerprintResult fingerprintResult = fingerprintPort.computeFingerprint(candidate);
|
||||
@@ -253,7 +253,7 @@ public class DefaultBatchRunProcessingUseCase implements BatchRunProcessingUseCa
|
||||
* @param error the fingerprint error
|
||||
*/
|
||||
private void handleFingerprintError(SourceDocumentCandidate candidate, FingerprintTechnicalError error) {
|
||||
LOG.warn("Fingerprint computation failed for '{}': {} — candidate skipped (not historised).",
|
||||
logger.warn("Fingerprint computation failed for '{}': {} — candidate skipped (not historised).",
|
||||
candidate.uniqueIdentifier(), error.errorMessage());
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ public class DefaultBatchRunProcessingUseCase implements BatchRunProcessingUseCa
|
||||
BatchRunContext context,
|
||||
Instant attemptStart) {
|
||||
DocumentFingerprint fingerprint = fingerprintSuccess.fingerprint();
|
||||
LOG.debug("Fingerprint computed for '{}': {}",
|
||||
logger.debug("Fingerprint computed for '{}': {}",
|
||||
candidate.uniqueIdentifier(), fingerprint.sha256Hex());
|
||||
|
||||
documentProcessingCoordinator.processDeferredOutcome(
|
||||
@@ -317,17 +317,17 @@ public class DefaultBatchRunProcessingUseCase implements BatchRunProcessingUseCa
|
||||
private void logExtractionResult(SourceDocumentCandidate candidate, PdfExtractionResult result) {
|
||||
switch (result) {
|
||||
case de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess success -> {
|
||||
LOG.debug("PDF extraction successful for '{}'. Pages: {}, Text length: {} chars.",
|
||||
logger.debug("PDF extraction successful for '{}'. Pages: {}, Text length: {} chars.",
|
||||
candidate.uniqueIdentifier(),
|
||||
success.pageCount().value(),
|
||||
success.extractedText().length());
|
||||
}
|
||||
case de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionContentError contentError -> {
|
||||
LOG.debug("PDF content extraction failed for '{}' (content problem): {}",
|
||||
logger.debug("PDF content extraction failed for '{}' (content problem): {}",
|
||||
candidate.uniqueIdentifier(), contentError.reason());
|
||||
}
|
||||
case de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError technicalError -> {
|
||||
LOG.debug("PDF extraction technical error for '{}': {}",
|
||||
logger.debug("PDF extraction technical error for '{}': {}",
|
||||
candidate.uniqueIdentifier(), technicalError.errorMessage());
|
||||
}
|
||||
default -> {
|
||||
@@ -345,15 +345,15 @@ public class DefaultBatchRunProcessingUseCase implements BatchRunProcessingUseCa
|
||||
private void logProcessingOutcome(SourceDocumentCandidate candidate, DocumentProcessingOutcome outcome) {
|
||||
switch (outcome) {
|
||||
case de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed passed -> {
|
||||
LOG.info("Pre-checks PASSED for '{}'. Candidate ready for persistence.",
|
||||
logger.info("Pre-checks PASSED for '{}'. Candidate ready for persistence.",
|
||||
candidate.uniqueIdentifier());
|
||||
}
|
||||
case de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed failed -> {
|
||||
LOG.info("Pre-checks FAILED for '{}': {} (Deterministic content error).",
|
||||
logger.info("Pre-checks FAILED for '{}': {} (Deterministic content error).",
|
||||
candidate.uniqueIdentifier(), failed.failureReasonDescription());
|
||||
}
|
||||
case de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError technicalError -> {
|
||||
LOG.warn("Processing FAILED for '{}': {} (Technical error – retryable).",
|
||||
logger.warn("Processing FAILED for '{}': {} (Technical error – retryable).",
|
||||
candidate.uniqueIdentifier(), technicalError.errorMessage());
|
||||
}
|
||||
default -> {
|
||||
|
||||
@@ -12,6 +12,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.FailureCounters;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceLookupTechnicalFailure;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
|
||||
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.UnitOfWorkPort;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||
@@ -71,7 +72,7 @@ class DocumentProcessingCoordinatorTest {
|
||||
recordRepo = new CapturingDocumentRecordRepository();
|
||||
attemptRepo = new CapturingProcessingAttemptRepository();
|
||||
unitOfWorkPort = new CapturingUnitOfWorkPort(recordRepo, attemptRepo);
|
||||
processor = new DocumentProcessingCoordinator(recordRepo, attemptRepo, unitOfWorkPort);
|
||||
processor = new DocumentProcessingCoordinator(recordRepo, attemptRepo, unitOfWorkPort, new NoOpProcessingLogger());
|
||||
|
||||
candidate = new SourceDocumentCandidate(
|
||||
"test.pdf", 1024L, new SourceDocumentLocator("/tmp/test.pdf"));
|
||||
@@ -467,4 +468,26 @@ class DocumentProcessingCoordinatorTest {
|
||||
operations.accept(mockOps);
|
||||
}
|
||||
}
|
||||
|
||||
private static class NoOpProcessingLogger implements ProcessingLogger {
|
||||
@Override
|
||||
public void info(String message, Object... args) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message, Object... args) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String message, Object... args) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message, Object... args) {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintTechnicalError
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.PdfTextExtractionPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
|
||||
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.RunLockPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockUnavailableException;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentAccessException;
|
||||
@@ -435,7 +436,8 @@ class BatchRunProcessingUseCaseTest {
|
||||
FingerprintPort fingerprintPort,
|
||||
DocumentProcessingCoordinator processor) {
|
||||
return new DefaultBatchRunProcessingUseCase(
|
||||
config, lockPort, candidatesPort, extractionPort, fingerprintPort, processor);
|
||||
config, lockPort, candidatesPort, extractionPort, fingerprintPort, processor,
|
||||
new NoOpProcessingLogger());
|
||||
}
|
||||
|
||||
private static StartConfiguration buildConfig(Path tempDir) throws Exception {
|
||||
@@ -617,7 +619,8 @@ class BatchRunProcessingUseCaseTest {
|
||||
*/
|
||||
private static class NoOpDocumentProcessingCoordinator extends DocumentProcessingCoordinator {
|
||||
NoOpDocumentProcessingCoordinator() {
|
||||
super(new NoOpDocumentRecordRepository(), new NoOpProcessingAttemptRepository(), new NoOpUnitOfWorkPort());
|
||||
super(new NoOpDocumentRecordRepository(), new NoOpProcessingAttemptRepository(), new NoOpUnitOfWorkPort(),
|
||||
new NoOpProcessingLogger());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,7 +631,8 @@ class BatchRunProcessingUseCaseTest {
|
||||
private int processCallCount = 0;
|
||||
|
||||
TrackingDocumentProcessingCoordinator() {
|
||||
super(new NoOpDocumentRecordRepository(), new NoOpProcessingAttemptRepository(), new NoOpUnitOfWorkPort());
|
||||
super(new NoOpDocumentRecordRepository(), new NoOpProcessingAttemptRepository(), new NoOpUnitOfWorkPort(),
|
||||
new NoOpProcessingLogger());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -705,12 +709,12 @@ class BatchRunProcessingUseCaseTest {
|
||||
public void saveProcessingAttempt(ProcessingAttempt attempt) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void createDocumentRecord(DocumentRecord record) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void updateDocumentRecord(DocumentRecord record) {
|
||||
// No-op
|
||||
@@ -718,4 +722,27 @@ class BatchRunProcessingUseCaseTest {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** No-op ProcessingLogger for use in test instances. */
|
||||
private static class NoOpProcessingLogger implements ProcessingLogger {
|
||||
@Override
|
||||
public void info(String message, Object... args) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message, Object... args) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String message, Object... args) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message, Object... args) {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,12 @@ 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.RunLockPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.service.DocumentProcessingCoordinator;
|
||||
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultBatchRunProcessingUseCase;
|
||||
import de.gecheckt.pdf.umbenenner.bootstrap.adapter.Log4jProcessingLogger;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
||||
|
||||
@@ -171,15 +173,17 @@ public class BootstrapRunner {
|
||||
new SqliteProcessingAttemptRepositoryAdapter(jdbcUrl);
|
||||
UnitOfWorkPort unitOfWorkPort =
|
||||
new SqliteUnitOfWorkAdapter(jdbcUrl);
|
||||
ProcessingLogger logger = new Log4jProcessingLogger(DefaultBatchRunProcessingUseCase.class);
|
||||
DocumentProcessingCoordinator documentProcessingCoordinator =
|
||||
new DocumentProcessingCoordinator(documentRecordRepository, processingAttemptRepository, unitOfWorkPort);
|
||||
new DocumentProcessingCoordinator(documentRecordRepository, processingAttemptRepository, unitOfWorkPort, logger);
|
||||
return new DefaultBatchRunProcessingUseCase(
|
||||
config,
|
||||
lock,
|
||||
new SourceDocumentCandidatesPortAdapter(config.sourceFolder()),
|
||||
new PdfTextExtractionPortAdapter(),
|
||||
fingerprintPort,
|
||||
documentProcessingCoordinator);
|
||||
documentProcessingCoordinator,
|
||||
logger);
|
||||
};
|
||||
this.commandFactory = SchedulerBatchCommand::new;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package de.gecheckt.pdf.umbenenner.bootstrap.adapter;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingLogger;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Log4j-based adapter implementing the {@link ProcessingLogger} port.
|
||||
* <p>
|
||||
* This adapter bridges the application layer to the concrete Log4j2 framework,
|
||||
* allowing the application to remain decoupled from logging technology choices.
|
||||
* <p>
|
||||
* The error method intelligently detects if the last argument is a Throwable
|
||||
* and logs accordingly.
|
||||
*/
|
||||
public class Log4jProcessingLogger implements ProcessingLogger {
|
||||
|
||||
private final Logger log4jLogger;
|
||||
|
||||
/**
|
||||
* Creates a logger instance for the given class.
|
||||
*
|
||||
* @param clazz the class to derive the logger name from; must not be null
|
||||
*/
|
||||
public Log4jProcessingLogger(Class<?> clazz) {
|
||||
this.log4jLogger = LogManager.getLogger(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message, Object... args) {
|
||||
log4jLogger.info(message, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message, Object... args) {
|
||||
log4jLogger.debug(message, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String message, Object... args) {
|
||||
log4jLogger.warn(message, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message, Object... args) {
|
||||
// If the last argument is a Throwable, extract it and pass separately
|
||||
if (args.length > 0 && args[args.length - 1] instanceof Throwable throwable) {
|
||||
Object[] messageArgs = new Object[args.length - 1];
|
||||
System.arraycopy(args, 0, messageArgs, 0, args.length - 1);
|
||||
log4jLogger.error(message, throwable, messageArgs);
|
||||
} else {
|
||||
log4jLogger.error(message, args);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user