SonarQube: fix alle BLOCKER- und CRITICAL-Issues (S3252, S2479, S1186, S1192, S2699, S5783, S3776)
- S3252: GuiStatusRefreshTimeline nutzt Animation.INDEFINITE statt Timeline.INDEFINITE - S2479: Narrow-No-Break-Space (U+202F) in GuiTooltipTexts durch normales Leerzeichen ersetzt - S1186: 134 leere Stub-Methoden in 18 Test- und Produktionsdateien kommentiert - S1192: ~49 duplizierte String-Literale in ~25 Klassen als Konstanten extrahiert - S2699: fehlende Assertions in SqliteSchemaInitializationAdapterTest und FilesystemTargetFolderAdapterTest ergaenzt - S5783: Lambda-geprufte Ausnahme in SqliteSchemaInitializationAdapterTest in private Hilfsmethode extrahiert - S3776: kognitive Komplexitaet in 8 Methoden durch Methodenextraktion auf unter 15 gesenkt (EarlyLogDirectoryInitializer, CliArgumentParser, GuiConfigurationEditorWorkspace, GuiHistoryTab x2, GuiBatchRunTab x2, DefaultManualFileCopyUseCase) - Kompilierungsfehler behoben: private-Modifier in CorrectionOutcome-Interface entfernt, selbstreferenzielle Konstante in ModelCatalogResult korrigiert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+20
-13
@@ -124,6 +124,12 @@ import javafx.stage.Window;
|
|||||||
* Thread via {@code Platform.runLater}.
|
* Thread via {@code Platform.runLater}.
|
||||||
*/
|
*/
|
||||||
public final class GuiConfigurationEditorWorkspace {
|
public final class GuiConfigurationEditorWorkspace {
|
||||||
|
private static final String NO_PROMPT_PATH_MSG = "Kein Prompt-Pfad konfiguriert.";
|
||||||
|
private static final String OPERATION_VALIDATE = "Validierung";
|
||||||
|
private static final String PROPERTIES_FILTER_EXT = "*.properties";
|
||||||
|
private static final String PROPERTIES_FILTER_DESC = "Properties-Dateien";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(GuiConfigurationEditorWorkspace.class);
|
private static final Logger LOG = LogManager.getLogger(GuiConfigurationEditorWorkspace.class);
|
||||||
private static final String WELCOME_TEXT =
|
private static final String WELCOME_TEXT =
|
||||||
@@ -985,7 +991,7 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
Window owner = root.getScene() == null ? null : root.getScene().getWindow();
|
Window owner = root.getScene() == null ? null : root.getScene().getWindow();
|
||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = new FileChooser();
|
||||||
fileChooser.setTitle("Konfiguration öffnen");
|
fileChooser.setTitle("Konfiguration öffnen");
|
||||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Properties-Dateien", "*.properties"));
|
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(PROPERTIES_FILTER_DESC, PROPERTIES_FILTER_EXT));
|
||||||
if (owner != null && editorState.hasLoadedFileSnapshot()) {
|
if (owner != null && editorState.hasLoadedFileSnapshot()) {
|
||||||
Path currentPath = editorState.loadedFileSnapshot().orElseThrow().filePath();
|
Path currentPath = editorState.loadedFileSnapshot().orElseThrow().filePath();
|
||||||
Path parent = currentPath.getParent();
|
Path parent = currentPath.getParent();
|
||||||
@@ -1055,7 +1061,7 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
FileChooser fileChooser = saveFileChooserFactory.get();
|
FileChooser fileChooser = saveFileChooserFactory.get();
|
||||||
fileChooser.setTitle("Konfiguration speichern");
|
fileChooser.setTitle("Konfiguration speichern");
|
||||||
fileChooser.getExtensionFilters().add(
|
fileChooser.getExtensionFilters().add(
|
||||||
new FileChooser.ExtensionFilter("Properties-Dateien", "*.properties"));
|
new FileChooser.ExtensionFilter(PROPERTIES_FILTER_DESC, PROPERTIES_FILTER_EXT));
|
||||||
|
|
||||||
// Propose the default path relative to the working directory.
|
// Propose the default path relative to the working directory.
|
||||||
Path proposedDir = DEFAULT_SAVE_PATH.getParent();
|
Path proposedDir = DEFAULT_SAVE_PATH.getParent();
|
||||||
@@ -1504,7 +1510,7 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
FileChooser fileChooser = saveFileChooserFactory.get();
|
FileChooser fileChooser = saveFileChooserFactory.get();
|
||||||
fileChooser.setTitle("Konfiguration speichern");
|
fileChooser.setTitle("Konfiguration speichern");
|
||||||
fileChooser.getExtensionFilters().add(
|
fileChooser.getExtensionFilters().add(
|
||||||
new FileChooser.ExtensionFilter("Properties-Dateien", "*.properties"));
|
new FileChooser.ExtensionFilter(PROPERTIES_FILTER_DESC, PROPERTIES_FILTER_EXT));
|
||||||
java.io.File proposedDirFile = DEFAULT_SAVE_PATH.getParent().toAbsolutePath().toFile();
|
java.io.File proposedDirFile = DEFAULT_SAVE_PATH.getParent().toAbsolutePath().toFile();
|
||||||
if (proposedDirFile.exists()) {
|
if (proposedDirFile.exists()) {
|
||||||
fileChooser.setInitialDirectory(proposedDirFile);
|
fileChooser.setInitialDirectory(proposedDirFile);
|
||||||
@@ -1604,13 +1610,13 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
@Override
|
@Override
|
||||||
public de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingResult loadCurrentPrompt() {
|
public de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingResult loadCurrentPrompt() {
|
||||||
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingFailure(
|
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingFailure(
|
||||||
"NO_PATH", "Kein Prompt-Pfad konfiguriert.");
|
"NO_PATH", NO_PROMPT_PATH_MSG);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult save(String content) {
|
public de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult save(String content) {
|
||||||
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult.WriteFailed(
|
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult.WriteFailed(
|
||||||
"Kein Prompt-Pfad konfiguriert.", null);
|
NO_PROMPT_PATH_MSG, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1619,7 +1625,7 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||||
.CorrectionSuggestion.CreatePromptFile suggestion) {
|
.CorrectionSuggestion.CreatePromptFile suggestion) {
|
||||||
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||||
.CorrectionOutcome.NotAttempted(suggestion, "Kein Prompt-Pfad konfiguriert.");
|
.CorrectionOutcome.NotAttempted(suggestion, NO_PROMPT_PATH_MSG);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1696,15 +1702,17 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
// Tab-Wechsel-Schutz: Beim Wechsel weg vom Verarbeitungslauf-Tab prüfen ob
|
// Tab-Wechsel-Schutz: Beim Wechsel weg vom Verarbeitungslauf-Tab prüfen ob
|
||||||
// der Dateiname-Editor ungespeicherte Änderungen hat.
|
// der Dateiname-Editor ungespeicherte Änderungen hat.
|
||||||
// Gleiches gilt für den Prompt-Tab.
|
// Gleiches gilt für den Prompt-Tab.
|
||||||
tabPane.getSelectionModel().selectedItemProperty().addListener((obs, oldTab, newTab) -> {
|
tabPane.getSelectionModel().selectedItemProperty().addListener(
|
||||||
|
(obs, oldTab, newTab) -> handleTabSwitch(oldTab, newTab));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTabSwitch(javafx.scene.control.Tab oldTab, javafx.scene.control.Tab newTab) {
|
||||||
if (oldTab == null || newTab == null) {
|
if (oldTab == null || newTab == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (oldTab == batchRunTab.tab() && batchRunTab.hasUnsavedFilenameEdits()) {
|
if (oldTab == batchRunTab.tab() && batchRunTab.hasUnsavedFilenameEdits()) {
|
||||||
// Selektion kurz unterdrücken um Rekursion zu vermeiden
|
|
||||||
boolean shouldDiscard = batchRunTab.confirmDiscardUnsavedFilenameEdits();
|
boolean shouldDiscard = batchRunTab.confirmDiscardUnsavedFilenameEdits();
|
||||||
if (!shouldDiscard) {
|
if (!shouldDiscard) {
|
||||||
// Zurück zum Verarbeitungslauf-Tab
|
|
||||||
Platform.runLater(() -> tabPane.getSelectionModel().select(oldTab));
|
Platform.runLater(() -> tabPane.getSelectionModel().select(oldTab));
|
||||||
}
|
}
|
||||||
} else if (oldTab == promptEditorTab.tab() && promptEditorTab.hasDirtyContent()) {
|
} else if (oldTab == promptEditorTab.tab() && promptEditorTab.hasDirtyContent()) {
|
||||||
@@ -1715,7 +1723,6 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
promptEditorTab.discardChanges();
|
promptEditorTab.discardChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureActionBar() {
|
private void configureActionBar() {
|
||||||
@@ -2610,7 +2617,7 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
|
|
||||||
for (EditorValidationFinding finding : report.findings()) {
|
for (EditorValidationFinding finding : report.findings()) {
|
||||||
GuiMessageSeverity severity = toGuiSeverity(finding.severity());
|
GuiMessageSeverity severity = toGuiSeverity(finding.severity());
|
||||||
messages.add(GuiMessageEntry.of(severity, finding.message(), "Validierung"));
|
messages.add(GuiMessageEntry.of(severity, finding.message(), OPERATION_VALIDATE));
|
||||||
if (finding.hasFieldKey()) {
|
if (finding.hasFieldKey()) {
|
||||||
fieldFindings.add(new GuiFieldFinding(finding.fieldKey().orElseThrow(),
|
fieldFindings.add(new GuiFieldFinding(finding.fieldKey().orElseThrow(),
|
||||||
severity, finding.message()));
|
severity, finding.message()));
|
||||||
@@ -2619,7 +2626,7 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
|
|
||||||
// Replace validation-related entries; preserve model-catalog messages (from coordinator)
|
// Replace validation-related entries; preserve model-catalog messages (from coordinator)
|
||||||
pendingMessages.removeIf(m -> m.source().isPresent()
|
pendingMessages.removeIf(m -> m.source().isPresent()
|
||||||
&& "Validierung".equals(m.source().get()));
|
&& OPERATION_VALIDATE.equals(m.source().get()));
|
||||||
pendingMessages.addAll(messages);
|
pendingMessages.addAll(messages);
|
||||||
|
|
||||||
pendingFieldFindings.clear();
|
pendingFieldFindings.clear();
|
||||||
@@ -2675,7 +2682,7 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
// Drop silent auto-validation entries so the central message area is not flooded
|
// Drop silent auto-validation entries so the central message area is not flooded
|
||||||
// by keystroke-level background checks; explicit action entries always accumulate.
|
// by keystroke-level background checks; explicit action entries always accumulate.
|
||||||
pendingMessages.removeIf(m -> m.source().isPresent()
|
pendingMessages.removeIf(m -> m.source().isPresent()
|
||||||
&& "Validierung".equals(m.source().get()));
|
&& OPERATION_VALIDATE.equals(m.source().get()));
|
||||||
|
|
||||||
// Append a timestamped confirmation plus each concrete finding as its own entry.
|
// Append a timestamped confirmation plus each concrete finding as its own entry.
|
||||||
int findingCount = report.findings().size();
|
int findingCount = report.findings().size();
|
||||||
|
|||||||
+12
-8
@@ -51,6 +51,10 @@ import javafx.application.Platform;
|
|||||||
* {@code Platform.runLater}.
|
* {@code Platform.runLater}.
|
||||||
*/
|
*/
|
||||||
public final class GuiModelCatalogCoordinator {
|
public final class GuiModelCatalogCoordinator {
|
||||||
|
private static final String LOG_MODEL_FETCH_FMT = "GUI-Modellabruf: {} (Provider: {})";
|
||||||
|
private static final String OPERATION_MODELLABRUF = "Modellabruf";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(GuiModelCatalogCoordinator.class);
|
private static final Logger LOG = LogManager.getLogger(GuiModelCatalogCoordinator.class);
|
||||||
|
|
||||||
@@ -203,7 +207,7 @@ public final class GuiModelCatalogCoordinator {
|
|||||||
String previousManualValue) {
|
String previousManualValue) {
|
||||||
// Remove any previous message entries from an earlier retrieval so messages do not
|
// Remove any previous message entries from an earlier retrieval so messages do not
|
||||||
// accumulate across repeated triggers of the same retrieval action.
|
// accumulate across repeated triggers of the same retrieval action.
|
||||||
pendingMessages.removeIf(msg -> "Modellabruf".equals(msg.source().orElse("")));
|
pendingMessages.removeIf(msg -> OPERATION_MODELLABRUF.equals(msg.source().orElse("")));
|
||||||
|
|
||||||
String displayName = displayNameFor(family);
|
String displayName = displayNameFor(family);
|
||||||
|
|
||||||
@@ -213,28 +217,28 @@ public final class GuiModelCatalogCoordinator {
|
|||||||
container.applyModelList(models, previousManualValue);
|
container.applyModelList(models, previousManualValue);
|
||||||
String message = "Modellliste für " + displayName + " geladen ("
|
String message = "Modellliste für " + displayName + " geladen ("
|
||||||
+ models.size() + " " + (models.size() == 1 ? "Eintrag" : "Einträge") + ").";
|
+ models.size() + " " + (models.size() == 1 ? "Eintrag" : "Einträge") + ").";
|
||||||
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.INFO, message, "Modellabruf"));
|
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.INFO, message, OPERATION_MODELLABRUF));
|
||||||
LOG.info("GUI-Modellabruf: {} (Provider: {})", message, family.getIdentifier());
|
LOG.info(LOG_MODEL_FETCH_FMT, message, family.getIdentifier());
|
||||||
}
|
}
|
||||||
case ModelCatalogResult.EmptyList emptyList -> {
|
case ModelCatalogResult.EmptyList emptyList -> {
|
||||||
container.applyManualFallback(GuiModelSource.LIST_UNAVAILABLE_MANUAL_INPUT);
|
container.applyManualFallback(GuiModelSource.LIST_UNAVAILABLE_MANUAL_INPUT);
|
||||||
String message = "Provider " + displayName
|
String message = "Provider " + displayName
|
||||||
+ " liefert aktuell keine Modelle. Manuelle Eingabe aktiv.";
|
+ " liefert aktuell keine Modelle. Manuelle Eingabe aktiv.";
|
||||||
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.HINT, message, "Modellabruf"));
|
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.HINT, message, OPERATION_MODELLABRUF));
|
||||||
LOG.warn("GUI-Modellabruf: {} (Provider: {})", message, family.getIdentifier());
|
LOG.warn(LOG_MODEL_FETCH_FMT, message, family.getIdentifier());
|
||||||
}
|
}
|
||||||
case ModelCatalogResult.IncompleteConfiguration incomplete -> {
|
case ModelCatalogResult.IncompleteConfiguration incomplete -> {
|
||||||
container.applyManualFallback(GuiModelSource.LIST_UNAVAILABLE_MANUAL_INPUT);
|
container.applyManualFallback(GuiModelSource.LIST_UNAVAILABLE_MANUAL_INPUT);
|
||||||
String message = "Modellliste nicht abrufbar: " + incomplete.missingReason()
|
String message = "Modellliste nicht abrufbar: " + incomplete.missingReason()
|
||||||
+ ". Manuelle Eingabe aktiv.";
|
+ ". Manuelle Eingabe aktiv.";
|
||||||
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.WARNING, message, "Modellabruf"));
|
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.WARNING, message, OPERATION_MODELLABRUF));
|
||||||
LOG.warn("GUI-Modellabruf: {} (Provider: {})", message, family.getIdentifier());
|
LOG.warn(LOG_MODEL_FETCH_FMT, message, family.getIdentifier());
|
||||||
}
|
}
|
||||||
case ModelCatalogResult.TechnicalFailure failure -> {
|
case ModelCatalogResult.TechnicalFailure failure -> {
|
||||||
container.applyManualFallback(GuiModelSource.LIST_FAILED_MANUAL_INPUT);
|
container.applyManualFallback(GuiModelSource.LIST_FAILED_MANUAL_INPUT);
|
||||||
String message = "Modellliste nicht abrufbar (" + failure.errorCategory()
|
String message = "Modellliste nicht abrufbar (" + failure.errorCategory()
|
||||||
+ "). Manuelle Eingabe aktiv.";
|
+ "). Manuelle Eingabe aktiv.";
|
||||||
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.ERROR, message, "Modellabruf"));
|
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.ERROR, message, OPERATION_MODELLABRUF));
|
||||||
LOG.warn("GUI-Modellabruf: {} Detail: {} (Provider: {})",
|
LOG.warn("GUI-Modellabruf: {} Detail: {} (Provider: {})",
|
||||||
message, failure.errorDetail(), family.getIdentifier());
|
message, failure.errorDetail(), family.getIdentifier());
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -54,6 +54,9 @@ import javafx.scene.layout.VBox;
|
|||||||
* Hintergrund-Worker-Thread ({@code gui-scheduler-control}) ausgeführt.
|
* Hintergrund-Worker-Thread ({@code gui-scheduler-control}) ausgeführt.
|
||||||
*/
|
*/
|
||||||
public final class GuiSchedulerTab {
|
public final class GuiSchedulerTab {
|
||||||
|
private static final String HEADER_LABEL_STYLE = "-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(GuiSchedulerTab.class);
|
private static final Logger LOG = LogManager.getLogger(GuiSchedulerTab.class);
|
||||||
|
|
||||||
@@ -177,7 +180,7 @@ public final class GuiSchedulerTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private VBox buildControlArea() {
|
private VBox buildControlArea() {
|
||||||
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;");
|
statusLabel.setStyle(HEADER_LABEL_STYLE);
|
||||||
|
|
||||||
stopButton.setDisable(true);
|
stopButton.setDisable(true);
|
||||||
HBox buttonBox = new HBox(10, startButton, stopButton);
|
HBox buttonBox = new HBox(10, startButton, stopButton);
|
||||||
@@ -248,7 +251,7 @@ public final class GuiSchedulerTab {
|
|||||||
switch (status.state()) {
|
switch (status.state()) {
|
||||||
case STOPPED -> {
|
case STOPPED -> {
|
||||||
statusLabel.setText("○ Gestoppt");
|
statusLabel.setText("○ Gestoppt");
|
||||||
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;");
|
statusLabel.setStyle(HEADER_LABEL_STYLE);
|
||||||
}
|
}
|
||||||
case STARTING -> {
|
case STARTING -> {
|
||||||
statusLabel.setText("⟳ Wird gestartet…");
|
statusLabel.setText("⟳ Wird gestartet…");
|
||||||
@@ -264,7 +267,7 @@ public final class GuiSchedulerTab {
|
|||||||
}
|
}
|
||||||
case STOPPING_BATCH_ACTIVE -> {
|
case STOPPING_BATCH_ACTIVE -> {
|
||||||
statusLabel.setText("○ Gestoppt – aktueller Lauf läuft noch");
|
statusLabel.setText("○ Gestoppt – aktueller Lauf läuft noch");
|
||||||
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;");
|
statusLabel.setStyle(HEADER_LABEL_STYLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-6
@@ -101,6 +101,9 @@ public record GuiStartupContext(
|
|||||||
Optional<String> applicationContextError,
|
Optional<String> applicationContextError,
|
||||||
Optional<SchedulerControlUseCase> schedulerControlUseCase,
|
Optional<SchedulerControlUseCase> schedulerControlUseCase,
|
||||||
Optional<ConfigurationFileLockPort> configurationFileLockPort) {
|
Optional<ConfigurationFileLockPort> configurationFileLockPort) {
|
||||||
|
private static final String NO_PROMPT_PORT_MSG = "Kein Prompt-Editor-Port in diesem Startkontext verfügbar.";
|
||||||
|
private static final String NO_PORT_MSG = "Kein Port in diesem Startkontext.";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a fully wired startup context.
|
* Creates a fully wired startup context.
|
||||||
@@ -524,21 +527,21 @@ public record GuiStartupContext(
|
|||||||
createDirectory(de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
createDirectory(de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||||
.CorrectionSuggestion.CreateDirectory suggestion) {
|
.CorrectionSuggestion.CreateDirectory suggestion) {
|
||||||
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||||
.CorrectionOutcome.NotAttempted(suggestion, "Kein Port in diesem Startkontext.");
|
.CorrectionOutcome.NotAttempted(suggestion, NO_PORT_MSG);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome
|
public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome
|
||||||
createPromptFile(de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
createPromptFile(de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||||
.CorrectionSuggestion.CreatePromptFile suggestion) {
|
.CorrectionSuggestion.CreatePromptFile suggestion) {
|
||||||
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||||
.CorrectionOutcome.NotAttempted(suggestion, "Kein Port in diesem Startkontext.");
|
.CorrectionOutcome.NotAttempted(suggestion, NO_PORT_MSG);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome
|
public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome
|
||||||
prepareSqlitePath(de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
prepareSqlitePath(de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||||
.CorrectionSuggestion.PrepareSqlitePath suggestion) {
|
.CorrectionSuggestion.PrepareSqlitePath suggestion) {
|
||||||
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||||
.CorrectionOutcome.NotAttempted(suggestion, "Kein Port in diesem Startkontext.");
|
.CorrectionOutcome.NotAttempted(suggestion, NO_PORT_MSG);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
CorrectionExecutionService noOpCorrectionService = new CorrectionExecutionService(noOpResourceCreationPort);
|
CorrectionExecutionService noOpCorrectionService = new CorrectionExecutionService(noOpResourceCreationPort);
|
||||||
@@ -599,13 +602,13 @@ public record GuiStartupContext(
|
|||||||
@Override
|
@Override
|
||||||
public de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingResult loadCurrentPrompt() {
|
public de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingResult loadCurrentPrompt() {
|
||||||
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingFailure(
|
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingFailure(
|
||||||
"NO_OP", "Kein Prompt-Editor-Port in diesem Startkontext verfügbar.");
|
"NO_OP", NO_PROMPT_PORT_MSG);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult save(String content) {
|
public de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult save(String content) {
|
||||||
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult.WriteFailed(
|
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult.WriteFailed(
|
||||||
"Kein Prompt-Editor-Port in diesem Startkontext verfügbar.", null);
|
NO_PROMPT_PORT_MSG, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -615,7 +618,7 @@ public record GuiStartupContext(
|
|||||||
.CorrectionSuggestion.CreatePromptFile suggestion) {
|
.CorrectionSuggestion.CreatePromptFile suggestion) {
|
||||||
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||||
.CorrectionOutcome.NotAttempted(
|
.CorrectionOutcome.NotAttempted(
|
||||||
suggestion, "Kein Prompt-Editor-Port in diesem Startkontext verfügbar.");
|
suggestion, NO_PROMPT_PORT_MSG);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -29,6 +29,9 @@ import javafx.scene.layout.Region;
|
|||||||
* Die Klasse selbst erzwingt dies nicht; der Aufrufer trägt die Verantwortung.
|
* Die Klasse selbst erzwingt dies nicht; der Aufrufer trägt die Verantwortung.
|
||||||
*/
|
*/
|
||||||
public final class GuiStatusBar {
|
public final class GuiStatusBar {
|
||||||
|
private static final String LABEL_STYLE = "-fx-font-size: 11px; -fx-text-fill: #555555;";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Anzeigetext wenn keine Konfiguration geladen ist. */
|
/** Anzeigetext wenn keine Konfiguration geladen ist. */
|
||||||
static final String KEIN_PROFIL_TEXT = "Kein Profil geladen";
|
static final String KEIN_PROFIL_TEXT = "Kein Profil geladen";
|
||||||
@@ -58,16 +61,16 @@ public final class GuiStatusBar {
|
|||||||
|
|
||||||
// Linkes Segment: Versionsanzeige
|
// Linkes Segment: Versionsanzeige
|
||||||
this.versionLabel = new Label(VERSION_PREFIX + this.applicationVersion);
|
this.versionLabel = new Label(VERSION_PREFIX + this.applicationVersion);
|
||||||
this.versionLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #555555;");
|
this.versionLabel.setStyle(LABEL_STYLE);
|
||||||
|
|
||||||
// Mittleres Segment: Provider und Modell
|
// Mittleres Segment: Provider und Modell
|
||||||
this.providerLabel = new Label(KEIN_PROFIL_TEXT);
|
this.providerLabel = new Label(KEIN_PROFIL_TEXT);
|
||||||
this.providerLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #555555;");
|
this.providerLabel.setStyle(LABEL_STYLE);
|
||||||
this.providerLabel.setAlignment(Pos.CENTER);
|
this.providerLabel.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
// Rechtes Segment: Konfigurationspfad
|
// Rechtes Segment: Konfigurationspfad
|
||||||
this.configPathLabel = new Label(KEIN_PROFIL_TEXT);
|
this.configPathLabel = new Label(KEIN_PROFIL_TEXT);
|
||||||
this.configPathLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #555555;");
|
this.configPathLabel.setStyle(LABEL_STYLE);
|
||||||
this.configPathLabel.setAlignment(Pos.CENTER_RIGHT);
|
this.configPathLabel.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
|
||||||
// Abstandhalter zwischen den Segmenten
|
// Abstandhalter zwischen den Segmenten
|
||||||
|
|||||||
+2
-1
@@ -4,6 +4,7 @@ import java.util.Objects;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase;
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase;
|
||||||
|
import javafx.animation.Animation;
|
||||||
import javafx.animation.KeyFrame;
|
import javafx.animation.KeyFrame;
|
||||||
import javafx.animation.Timeline;
|
import javafx.animation.Timeline;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
@@ -42,7 +43,7 @@ public final class GuiStatusRefreshTimeline {
|
|||||||
Objects.requireNonNull(onRefresh, "onRefresh must not be null");
|
Objects.requireNonNull(onRefresh, "onRefresh must not be null");
|
||||||
this.timeline = new Timeline(
|
this.timeline = new Timeline(
|
||||||
new KeyFrame(Duration.seconds(1), e -> onRefresh.run()));
|
new KeyFrame(Duration.seconds(1), e -> onRefresh.run()));
|
||||||
this.timeline.setCycleCount(Timeline.INDEFINITE);
|
this.timeline.setCycleCount(Animation.INDEFINITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -87,7 +87,7 @@ public final class GuiTooltipTexts {
|
|||||||
|
|
||||||
/** Tooltip für das Eingabefeld „Basis-URL". */
|
/** Tooltip für das Eingabefeld „Basis-URL". */
|
||||||
public static final String PROVIDER_BASIS_URL =
|
public static final String PROVIDER_BASIS_URL =
|
||||||
"Basis-URL des KI-Dienstes (z. B. https://api.openai.com/v1).";
|
"Basis-URL des KI-Dienstes (z. B. https://api.openai.com/v1).";
|
||||||
|
|
||||||
/** Tooltip für das Eingabefeld „Timeout". */
|
/** Tooltip für das Eingabefeld „Timeout". */
|
||||||
public static final String PROVIDER_TIMEOUT =
|
public static final String PROVIDER_TIMEOUT =
|
||||||
|
|||||||
+7
-4
@@ -63,6 +63,9 @@ import javafx.scene.control.Alert;
|
|||||||
* </ol>
|
* </ol>
|
||||||
*/
|
*/
|
||||||
public final class GuiBatchRunCoordinator {
|
public final class GuiBatchRunCoordinator {
|
||||||
|
private static final String CONFIG_FILE_NOT_NULL = "configFilePath must not be null";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(GuiBatchRunCoordinator.class);
|
private static final Logger LOG = LogManager.getLogger(GuiBatchRunCoordinator.class);
|
||||||
private static final String WORKER_THREAD_NAME = "gui-batch-run";
|
private static final String WORKER_THREAD_NAME = "gui-batch-run";
|
||||||
@@ -353,7 +356,7 @@ public final class GuiBatchRunCoordinator {
|
|||||||
* @throws NullPointerException if {@code configFilePath} is {@code null}
|
* @throws NullPointerException if {@code configFilePath} is {@code null}
|
||||||
*/
|
*/
|
||||||
public boolean start(Path configFilePath) {
|
public boolean start(Path configFilePath) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -379,7 +382,7 @@ public final class GuiBatchRunCoordinator {
|
|||||||
*/
|
*/
|
||||||
public boolean startMiniRun(Path configFilePath,
|
public boolean startMiniRun(Path configFilePath,
|
||||||
Set<DocumentFingerprint> fingerprintFilter) {
|
Set<DocumentFingerprint> fingerprintFilter) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
Objects.requireNonNull(fingerprintFilter, "fingerprintFilter must not be null");
|
Objects.requireNonNull(fingerprintFilter, "fingerprintFilter must not be null");
|
||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -411,7 +414,7 @@ public final class GuiBatchRunCoordinator {
|
|||||||
*/
|
*/
|
||||||
public boolean startReprocessing(Path configFilePath,
|
public boolean startReprocessing(Path configFilePath,
|
||||||
Set<DocumentFingerprint> fingerprintFilter) {
|
Set<DocumentFingerprint> fingerprintFilter) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
Objects.requireNonNull(fingerprintFilter, "fingerprintFilter must not be null");
|
Objects.requireNonNull(fingerprintFilter, "fingerprintFilter must not be null");
|
||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -452,7 +455,7 @@ public final class GuiBatchRunCoordinator {
|
|||||||
* @throws NullPointerException if any argument is {@code null}
|
* @throws NullPointerException if any argument is {@code null}
|
||||||
*/
|
*/
|
||||||
public boolean startReset(Path configFilePath, Set<DocumentFingerprint> fingerprints) {
|
public boolean startReset(Path configFilePath, Set<DocumentFingerprint> fingerprints) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
Objects.requireNonNull(fingerprints, "fingerprints must not be null");
|
Objects.requireNonNull(fingerprints, "fingerprints must not be null");
|
||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+70
-60
@@ -111,6 +111,11 @@ import javafx.scene.layout.VBox;
|
|||||||
* dafür, Hintergrundereignisse vor dem Callback auf den FX-Thread zu übertragen.
|
* dafür, Hintergrundereignisse vor dem Callback auf den FX-Thread zu übertragen.
|
||||||
*/
|
*/
|
||||||
public final class GuiBatchRunTab {
|
public final class GuiBatchRunTab {
|
||||||
|
private static final String COPY_FAILED_LOG = "Manuelle Dateikopie fehlgeschlagen: {}";
|
||||||
|
private static final String RENAME_FAILED_LOG = "Manuelle Dateiumbenennung fehlgeschlagen: {}";
|
||||||
|
private static final String DIRTY_STATE_MSG = "Dateiname-Editor: Ungespeicherte Änderungen";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(GuiBatchRunTab.class);
|
private static final Logger LOG = LogManager.getLogger(GuiBatchRunTab.class);
|
||||||
|
|
||||||
@@ -820,7 +825,7 @@ public final class GuiBatchRunTab {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fileNameEditor.discardChanges();
|
fileNameEditor.discardChanges();
|
||||||
LOG.debug("Dateiname-Editor: Ungespeicherte Änderung – Benutzer hat verworfen");
|
LOG.debug(DIRTY_STATE_MSG);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neue Zeile laden
|
// Neue Zeile laden
|
||||||
@@ -957,16 +962,35 @@ public final class GuiBatchRunTab {
|
|||||||
*/
|
*/
|
||||||
private void handleCopyResult(ManualFileCopyResult result, GuiBatchRunResultRow row) {
|
private void handleCopyResult(ManualFileCopyResult result, GuiBatchRunResultRow row) {
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case ManualFileCopySuccess success -> {
|
case ManualFileCopySuccess success -> applyCopySuccess(success, row);
|
||||||
|
case ManualFileCopyNoOpIdenticalTarget noOp -> applyCopyNoOpIdentical(noOp, row);
|
||||||
|
case ManualFileCopyDocumentNotFound notFound -> {
|
||||||
|
LOG.warn(COPY_FAILED_LOG, notFound.reason());
|
||||||
|
showMessage("Fehler: Dokument nicht gefunden – " + notFound.reason());
|
||||||
|
}
|
||||||
|
case ManualFileCopyInvalidState invalidState -> {
|
||||||
|
LOG.warn(COPY_FAILED_LOG, invalidState.reason());
|
||||||
|
showMessage("Fehler: Ungültiger Dokumentstatus – " + invalidState.reason());
|
||||||
|
}
|
||||||
|
case ManualFileCopyFileSystemFailure fsFail -> {
|
||||||
|
LOG.warn(COPY_FAILED_LOG, fsFail.message());
|
||||||
|
showMessage("Dateisystemfehler: " + fsFail.message());
|
||||||
|
}
|
||||||
|
case ManualFileCopyPersistenceFailure persistFail -> {
|
||||||
|
LOG.warn(COPY_FAILED_LOG, persistFail.message());
|
||||||
|
showMessage("Persistenzfehler (Zielkopie wurde zurückgerollt): " + persistFail.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyCopySuccess(ManualFileCopySuccess success, GuiBatchRunResultRow row) {
|
||||||
LOG.info("Manuelle Dateikopie erfolgreich: {} → {} (Suffix: {})",
|
LOG.info("Manuelle Dateikopie erfolgreich: {} → {} (Suffix: {})",
|
||||||
row.originalFileName(), success.appliedFileName(),
|
row.originalFileName(), success.appliedFileName(), success.conflictSuffixApplied());
|
||||||
success.conflictSuffixApplied());
|
|
||||||
GuiBatchRunResultRow updatedRow = buildSuccessRowAfterCopy(row, success.appliedFileName());
|
GuiBatchRunResultRow updatedRow = buildSuccessRowAfterCopy(row, success.appliedFileName());
|
||||||
currentlySelectedRow = updatedRow;
|
currentlySelectedRow = updatedRow;
|
||||||
fileNameEditor.clearDirtyState();
|
fileNameEditor.clearDirtyState();
|
||||||
upsertResultRowByFingerprint(updatedRow);
|
upsertResultRowByFingerprint(updatedRow);
|
||||||
String targetFolder = targetFolderSupplier.get().orElse("");
|
fileNameEditor.loadSelection(updatedRow, targetFolderSupplier.get().orElse(""));
|
||||||
fileNameEditor.loadSelection(updatedRow, targetFolder);
|
|
||||||
String msg = "Datei kopiert und gespeichert: " + success.appliedFileName();
|
String msg = "Datei kopiert und gespeichert: " + success.appliedFileName();
|
||||||
if (success.conflictSuffixApplied()) {
|
if (success.conflictSuffixApplied()) {
|
||||||
msg += " (Suffix wegen Namenskonflikt angehängt)";
|
msg += " (Suffix wegen Namenskonflikt angehängt)";
|
||||||
@@ -974,37 +998,18 @@ public final class GuiBatchRunTab {
|
|||||||
showMessage(msg);
|
showMessage(msg);
|
||||||
refreshAggregateCountersFromItems();
|
refreshAggregateCountersFromItems();
|
||||||
}
|
}
|
||||||
case ManualFileCopyNoOpIdenticalTarget noOp -> {
|
|
||||||
|
private void applyCopyNoOpIdentical(ManualFileCopyNoOpIdenticalTarget noOp, GuiBatchRunResultRow row) {
|
||||||
LOG.info("Manuelle Dateikopie: identische Zieldatei {} bereits vorhanden – kein Schreibvorgang.",
|
LOG.info("Manuelle Dateikopie: identische Zieldatei {} bereits vorhanden – kein Schreibvorgang.",
|
||||||
noOp.existingFileName());
|
noOp.existingFileName());
|
||||||
GuiBatchRunResultRow updatedRow = buildSuccessRowAfterCopy(row, noOp.existingFileName());
|
GuiBatchRunResultRow updatedRow = buildSuccessRowAfterCopy(row, noOp.existingFileName());
|
||||||
currentlySelectedRow = updatedRow;
|
currentlySelectedRow = updatedRow;
|
||||||
fileNameEditor.clearDirtyState();
|
fileNameEditor.clearDirtyState();
|
||||||
upsertResultRowByFingerprint(updatedRow);
|
upsertResultRowByFingerprint(updatedRow);
|
||||||
String targetFolder = targetFolderSupplier.get().orElse("");
|
fileNameEditor.loadSelection(updatedRow, targetFolderSupplier.get().orElse(""));
|
||||||
fileNameEditor.loadSelection(updatedRow, targetFolder);
|
|
||||||
showMessage("Identische Datei bereits vorhanden – Status auf SUCCESS gesetzt");
|
showMessage("Identische Datei bereits vorhanden – Status auf SUCCESS gesetzt");
|
||||||
refreshAggregateCountersFromItems();
|
refreshAggregateCountersFromItems();
|
||||||
}
|
}
|
||||||
case ManualFileCopyDocumentNotFound notFound -> {
|
|
||||||
LOG.warn("Manuelle Dateikopie fehlgeschlagen: {}", notFound.reason());
|
|
||||||
showMessage("Fehler: Dokument nicht gefunden – " + notFound.reason());
|
|
||||||
}
|
|
||||||
case ManualFileCopyInvalidState invalidState -> {
|
|
||||||
LOG.warn("Manuelle Dateikopie fehlgeschlagen: {}", invalidState.reason());
|
|
||||||
showMessage("Fehler: Ungültiger Dokumentstatus – " + invalidState.reason());
|
|
||||||
}
|
|
||||||
case ManualFileCopyFileSystemFailure fsFail -> {
|
|
||||||
LOG.warn("Manuelle Dateikopie fehlgeschlagen: {}", fsFail.message());
|
|
||||||
showMessage("Dateisystemfehler: " + fsFail.message());
|
|
||||||
}
|
|
||||||
case ManualFileCopyPersistenceFailure persistFail -> {
|
|
||||||
LOG.warn("Manuelle Dateikopie fehlgeschlagen: {}", persistFail.message());
|
|
||||||
showMessage("Persistenzfehler (Zielkopie wurde zurückgerollt): "
|
|
||||||
+ persistFail.message());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Baut eine neue Zeilen-Sicht für ein Dokument, das per manueller Dateikopie auf
|
* Baut eine neue Zeilen-Sicht für ein Dokument, das per manueller Dateikopie auf
|
||||||
@@ -1105,24 +1110,24 @@ public final class GuiBatchRunTab {
|
|||||||
noOp.existingFileName());
|
noOp.existingFileName());
|
||||||
}
|
}
|
||||||
case ManualFileRenameDocumentNotFound notFound -> {
|
case ManualFileRenameDocumentNotFound notFound -> {
|
||||||
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}", notFound.reason());
|
LOG.warn(RENAME_FAILED_LOG, notFound.reason());
|
||||||
showMessage("Fehler: Dokument nicht gefunden – " + notFound.reason());
|
showMessage("Fehler: Dokument nicht gefunden – " + notFound.reason());
|
||||||
}
|
}
|
||||||
case ManualFileRenameInvalidState invalidState -> {
|
case ManualFileRenameInvalidState invalidState -> {
|
||||||
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}", invalidState.reason());
|
LOG.warn(RENAME_FAILED_LOG, invalidState.reason());
|
||||||
showMessage("Fehler: Ungültiger Dokumentstatus – " + invalidState.reason());
|
showMessage("Fehler: Ungültiger Dokumentstatus – " + invalidState.reason());
|
||||||
}
|
}
|
||||||
case ManualFileRenameSourceFileMissing sourceMissing -> {
|
case ManualFileRenameSourceFileMissing sourceMissing -> {
|
||||||
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}",
|
LOG.warn(RENAME_FAILED_LOG,
|
||||||
sourceMissing.expectedFileName());
|
sourceMissing.expectedFileName());
|
||||||
showMessage("Zieldatei nicht gefunden – Umbenennung nicht möglich");
|
showMessage("Zieldatei nicht gefunden – Umbenennung nicht möglich");
|
||||||
}
|
}
|
||||||
case ManualFileRenameFileSystemFailure fsFail -> {
|
case ManualFileRenameFileSystemFailure fsFail -> {
|
||||||
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}", fsFail.message());
|
LOG.warn(RENAME_FAILED_LOG, fsFail.message());
|
||||||
showMessage("Dateisystemfehler: " + fsFail.message());
|
showMessage("Dateisystemfehler: " + fsFail.message());
|
||||||
}
|
}
|
||||||
case ManualFileRenamePersistenceFailure persistFail -> {
|
case ManualFileRenamePersistenceFailure persistFail -> {
|
||||||
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}", persistFail.message());
|
LOG.warn(RENAME_FAILED_LOG, persistFail.message());
|
||||||
showMessage("Persistenzfehler (Dateisystem wurde zurückgerollt): "
|
showMessage("Persistenzfehler (Dateisystem wurde zurückgerollt): "
|
||||||
+ persistFail.message());
|
+ persistFail.message());
|
||||||
}
|
}
|
||||||
@@ -1263,7 +1268,7 @@ public final class GuiBatchRunTab {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fileNameEditor.discardChanges();
|
fileNameEditor.discardChanges();
|
||||||
LOG.debug("Dateiname-Editor: Ungespeicherte Änderung – Benutzer hat verworfen");
|
LOG.debug(DIRTY_STATE_MSG);
|
||||||
}
|
}
|
||||||
if (!savedConfigurationReadyCheck.getAsBoolean()) {
|
if (!savedConfigurationReadyCheck.getAsBoolean()) {
|
||||||
showMessage(NO_SAVED_CONFIGURATION_HINT);
|
showMessage(NO_SAVED_CONFIGURATION_HINT);
|
||||||
@@ -1317,7 +1322,7 @@ public final class GuiBatchRunTab {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fileNameEditor.discardChanges();
|
fileNameEditor.discardChanges();
|
||||||
LOG.debug("Dateiname-Editor: Ungespeicherte Änderung – Benutzer hat verworfen");
|
LOG.debug(DIRTY_STATE_MSG);
|
||||||
}
|
}
|
||||||
if (!savedConfigurationReadyCheck.getAsBoolean()) {
|
if (!savedConfigurationReadyCheck.getAsBoolean()) {
|
||||||
showMessage(NO_SAVED_CONFIGURATION_HINT);
|
showMessage(NO_SAVED_CONFIGURATION_HINT);
|
||||||
@@ -1562,35 +1567,12 @@ public final class GuiBatchRunTab {
|
|||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
if (row.status() == DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED) {
|
if (row.status() == DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED) {
|
||||||
builder.append('\n');
|
return appendSkippedAlreadyProcessed(builder, row);
|
||||||
row.historicalContext().ifPresentOrElse(ctx -> {
|
|
||||||
ctx.lastSuccessInstant().ifPresentOrElse(
|
|
||||||
instant -> builder.append("Bereits erfolgreich verarbeitet am ")
|
|
||||||
.append(DETAIL_DATE_FORMAT.format(
|
|
||||||
instant.atZone(ZoneId.systemDefault())))
|
|
||||||
.append('.'),
|
|
||||||
() -> builder.append("Bereits erfolgreich verarbeitet."));
|
|
||||||
ctx.lastTargetFileName().ifPresent(name ->
|
|
||||||
builder.append('\n').append("Zieldatei: ").append(name).append('.'));
|
|
||||||
}, () -> builder.append("Bereits erfolgreich verarbeitet."));
|
|
||||||
return builder.toString();
|
|
||||||
}
|
}
|
||||||
if (row.status() == DocumentCompletionStatus.SKIPPED_FINAL_FAILURE) {
|
if (row.status() == DocumentCompletionStatus.SKIPPED_FINAL_FAILURE) {
|
||||||
builder.append('\n');
|
return appendSkippedFinalFailure(builder, row);
|
||||||
row.historicalContext().ifPresentOrElse(ctx ->
|
|
||||||
ctx.lastFailureInstant().ifPresentOrElse(
|
|
||||||
instant -> builder.append("Endg\u00fcltig fehlgeschlagen am ")
|
|
||||||
.append(DETAIL_DATE_FORMAT.format(
|
|
||||||
instant.atZone(ZoneId.systemDefault())))
|
|
||||||
.append(". Erneute Verarbeitung nur nach Reset m\u00f6glich."),
|
|
||||||
() -> builder.append(
|
|
||||||
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich.")),
|
|
||||||
() -> builder.append(
|
|
||||||
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich."));
|
|
||||||
return builder.toString();
|
|
||||||
}
|
}
|
||||||
if (row.status() == DocumentCompletionStatus.FAILED_PERMANENT) {
|
if (row.status() == DocumentCompletionStatus.FAILED_PERMANENT) {
|
||||||
// Erweiterter Erkl\u00e4rungstext gem\u00e4\u00df Spezifikation #51 \u2013 dauerhaft fehlgeschlagen
|
|
||||||
builder.append('\n').append(ProcessingStatusPresentation.DETAIL_TEXT_FAILED_PERMANENT);
|
builder.append('\n').append(ProcessingStatusPresentation.DETAIL_TEXT_FAILED_PERMANENT);
|
||||||
row.aiFailureMessage().ifPresent(msg ->
|
row.aiFailureMessage().ifPresent(msg ->
|
||||||
builder.append("\n\nFehlerdetail: ")
|
builder.append("\n\nFehlerdetail: ")
|
||||||
@@ -1611,6 +1593,34 @@ public final class GuiBatchRunTab {
|
|||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String appendSkippedAlreadyProcessed(StringBuilder builder, GuiBatchRunResultRow row) {
|
||||||
|
builder.append('\n');
|
||||||
|
row.historicalContext().ifPresentOrElse(ctx -> {
|
||||||
|
ctx.lastSuccessInstant().ifPresentOrElse(
|
||||||
|
instant -> builder.append("Bereits erfolgreich verarbeitet am ")
|
||||||
|
.append(DETAIL_DATE_FORMAT.format(instant.atZone(ZoneId.systemDefault())))
|
||||||
|
.append('.'),
|
||||||
|
() -> builder.append("Bereits erfolgreich verarbeitet."));
|
||||||
|
ctx.lastTargetFileName().ifPresent(name ->
|
||||||
|
builder.append('\n').append("Zieldatei: ").append(name).append('.'));
|
||||||
|
}, () -> builder.append("Bereits erfolgreich verarbeitet."));
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String appendSkippedFinalFailure(StringBuilder builder, GuiBatchRunResultRow row) {
|
||||||
|
builder.append('\n');
|
||||||
|
row.historicalContext().ifPresentOrElse(ctx ->
|
||||||
|
ctx.lastFailureInstant().ifPresentOrElse(
|
||||||
|
instant -> builder.append("Endg\u00fcltig fehlgeschlagen am ")
|
||||||
|
.append(DETAIL_DATE_FORMAT.format(instant.atZone(ZoneId.systemDefault())))
|
||||||
|
.append(". Erneute Verarbeitung nur nach Reset m\u00f6glich."),
|
||||||
|
() -> builder.append(
|
||||||
|
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich.")),
|
||||||
|
() -> builder.append(
|
||||||
|
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich."));
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private static GuiBatchRunLaunchOutcome rejectingMiniLaunch(
|
private static GuiBatchRunLaunchOutcome rejectingMiniLaunch(
|
||||||
Path p, Set<DocumentFingerprint> f,
|
Path p, Set<DocumentFingerprint> f,
|
||||||
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver o,
|
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver o,
|
||||||
|
|||||||
+9
-6
@@ -20,6 +20,9 @@ import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
|
|||||||
* Alle Methoden sind statisch.
|
* Alle Methoden sind statisch.
|
||||||
*/
|
*/
|
||||||
public final class ProcessingStatusPresentation {
|
public final class ProcessingStatusPresentation {
|
||||||
|
private static final String STATUS_NOT_NULL = "status darf nicht null sein";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Icons (Unicode-Zeichen, zuverlässig darstellbar unter Windows 10+)
|
// Icons (Unicode-Zeichen, zuverlässig darstellbar unter Windows 10+)
|
||||||
@@ -166,7 +169,7 @@ public final class ProcessingStatusPresentation {
|
|||||||
* @throws NullPointerException wenn {@code status} {@code null} ist
|
* @throws NullPointerException wenn {@code status} {@code null} ist
|
||||||
*/
|
*/
|
||||||
public static String iconFor(DocumentCompletionStatus status) {
|
public static String iconFor(DocumentCompletionStatus status) {
|
||||||
Objects.requireNonNull(status, "status darf nicht null sein");
|
Objects.requireNonNull(status, STATUS_NOT_NULL);
|
||||||
return switch (status) {
|
return switch (status) {
|
||||||
case SUCCESS -> ICON_SUCCESS;
|
case SUCCESS -> ICON_SUCCESS;
|
||||||
case FAILED_RETRYABLE -> ICON_FAILED_RETRYABLE;
|
case FAILED_RETRYABLE -> ICON_FAILED_RETRYABLE;
|
||||||
@@ -187,7 +190,7 @@ public final class ProcessingStatusPresentation {
|
|||||||
* @throws NullPointerException wenn {@code status} {@code null} ist
|
* @throws NullPointerException wenn {@code status} {@code null} ist
|
||||||
*/
|
*/
|
||||||
public static String cssColorFor(DocumentCompletionStatus status) {
|
public static String cssColorFor(DocumentCompletionStatus status) {
|
||||||
Objects.requireNonNull(status, "status darf nicht null sein");
|
Objects.requireNonNull(status, STATUS_NOT_NULL);
|
||||||
return switch (status) {
|
return switch (status) {
|
||||||
case SUCCESS -> COLOR_SUCCESS;
|
case SUCCESS -> COLOR_SUCCESS;
|
||||||
case FAILED_RETRYABLE -> COLOR_FAILED_RETRYABLE;
|
case FAILED_RETRYABLE -> COLOR_FAILED_RETRYABLE;
|
||||||
@@ -205,7 +208,7 @@ public final class ProcessingStatusPresentation {
|
|||||||
* @throws NullPointerException wenn {@code status} {@code null} ist
|
* @throws NullPointerException wenn {@code status} {@code null} ist
|
||||||
*/
|
*/
|
||||||
public static String tooltipFor(DocumentCompletionStatus status) {
|
public static String tooltipFor(DocumentCompletionStatus status) {
|
||||||
Objects.requireNonNull(status, "status darf nicht null sein");
|
Objects.requireNonNull(status, STATUS_NOT_NULL);
|
||||||
return switch (status) {
|
return switch (status) {
|
||||||
case SUCCESS -> TOOLTIP_SUCCESS;
|
case SUCCESS -> TOOLTIP_SUCCESS;
|
||||||
case FAILED_RETRYABLE -> TOOLTIP_FAILED_RETRYABLE;
|
case FAILED_RETRYABLE -> TOOLTIP_FAILED_RETRYABLE;
|
||||||
@@ -224,7 +227,7 @@ public final class ProcessingStatusPresentation {
|
|||||||
* @throws NullPointerException wenn {@code status} {@code null} ist
|
* @throws NullPointerException wenn {@code status} {@code null} ist
|
||||||
*/
|
*/
|
||||||
public static String summaryCategoryFor(DocumentCompletionStatus status) {
|
public static String summaryCategoryFor(DocumentCompletionStatus status) {
|
||||||
Objects.requireNonNull(status, "status darf nicht null sein");
|
Objects.requireNonNull(status, STATUS_NOT_NULL);
|
||||||
return switch (status) {
|
return switch (status) {
|
||||||
case SUCCESS -> SUMMARY_CATEGORY_SUCCESS;
|
case SUCCESS -> SUMMARY_CATEGORY_SUCCESS;
|
||||||
case FAILED_RETRYABLE -> SUMMARY_CATEGORY_FAILED_RETRYABLE;
|
case FAILED_RETRYABLE -> SUMMARY_CATEGORY_FAILED_RETRYABLE;
|
||||||
@@ -243,7 +246,7 @@ public final class ProcessingStatusPresentation {
|
|||||||
* @throws NullPointerException wenn {@code status} {@code null} ist
|
* @throws NullPointerException wenn {@code status} {@code null} ist
|
||||||
*/
|
*/
|
||||||
public static StatusVisuals visualsFor(DocumentCompletionStatus status) {
|
public static StatusVisuals visualsFor(DocumentCompletionStatus status) {
|
||||||
Objects.requireNonNull(status, "status darf nicht null sein");
|
Objects.requireNonNull(status, STATUS_NOT_NULL);
|
||||||
return new StatusVisuals(
|
return new StatusVisuals(
|
||||||
iconFor(status),
|
iconFor(status),
|
||||||
cssColorFor(status),
|
cssColorFor(status),
|
||||||
@@ -264,7 +267,7 @@ public final class ProcessingStatusPresentation {
|
|||||||
* @throws NullPointerException wenn {@code status} {@code null} ist
|
* @throws NullPointerException wenn {@code status} {@code null} ist
|
||||||
*/
|
*/
|
||||||
public static String displayTextFor(ProcessingStatus status) {
|
public static String displayTextFor(ProcessingStatus status) {
|
||||||
Objects.requireNonNull(status, "status darf nicht null sein");
|
Objects.requireNonNull(status, STATUS_NOT_NULL);
|
||||||
return switch (status) {
|
return switch (status) {
|
||||||
case SUCCESS -> "✓ Erfolgreich";
|
case SUCCESS -> "✓ Erfolgreich";
|
||||||
case FAILED_RETRYABLE -> "↻ Temporärer Fehler";
|
case FAILED_RETRYABLE -> "↻ Temporärer Fehler";
|
||||||
|
|||||||
+48
-37
@@ -87,6 +87,11 @@ import javafx.scene.layout.VBox;
|
|||||||
* Verarbeitungslaufs deaktiviert.
|
* Verarbeitungslaufs deaktiviert.
|
||||||
*/
|
*/
|
||||||
public final class GuiHistoryTab {
|
public final class GuiHistoryTab {
|
||||||
|
private static final String BOLD_STYLE = "-fx-font-weight: bold;";
|
||||||
|
private static final String NO_ERROR_DETAILS_MSG = "Keine Fehlerdetails gespeichert.";
|
||||||
|
private static final String NO_CONFIG_LOADED_MSG = "Keine Konfiguration geladen.";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(GuiHistoryTab.class);
|
private static final Logger LOG = LogManager.getLogger(GuiHistoryTab.class);
|
||||||
|
|
||||||
@@ -421,20 +426,20 @@ public final class GuiHistoryTab {
|
|||||||
addDetailRow(5, "Aktualisiert:", detailUpdatedLabel);
|
addDetailRow(5, "Aktualisiert:", detailUpdatedLabel);
|
||||||
|
|
||||||
Label detailTitle = new Label("Dokument-Details");
|
Label detailTitle = new Label("Dokument-Details");
|
||||||
detailTitle.setStyle("-fx-font-weight: bold;");
|
detailTitle.setStyle(BOLD_STYLE);
|
||||||
|
|
||||||
// Versuche-Tabelle
|
// Versuche-Tabelle
|
||||||
buildAttemptsTable();
|
buildAttemptsTable();
|
||||||
Label attemptsTitle = new Label("Verarbeitungsversuche");
|
Label attemptsTitle = new Label("Verarbeitungsversuche");
|
||||||
attemptsTitle.setStyle("-fx-font-weight: bold;");
|
attemptsTitle.setStyle(BOLD_STYLE);
|
||||||
|
|
||||||
// Fehlerursache (aus letztem Fehler-Versuch)
|
// Fehlerursache (aus letztem Fehler-Versuch)
|
||||||
failureArea.setEditable(false);
|
failureArea.setEditable(false);
|
||||||
failureArea.setWrapText(true);
|
failureArea.setWrapText(true);
|
||||||
failureArea.setPrefRowCount(3);
|
failureArea.setPrefRowCount(3);
|
||||||
failureArea.setPromptText("Keine Fehlerdetails gespeichert.");
|
failureArea.setPromptText(NO_ERROR_DETAILS_MSG);
|
||||||
Label failureTitle = new Label("Fehlerursache (letzter Fehler-Versuch)");
|
Label failureTitle = new Label("Fehlerursache (letzter Fehler-Versuch)");
|
||||||
failureTitle.setStyle("-fx-font-weight: bold;");
|
failureTitle.setStyle(BOLD_STYLE);
|
||||||
|
|
||||||
failureArea.setTooltip(new Tooltip(GuiTooltipTexts.VERLAUF_FAILURE_AREA));
|
failureArea.setTooltip(new Tooltip(GuiTooltipTexts.VERLAUF_FAILURE_AREA));
|
||||||
|
|
||||||
@@ -445,7 +450,7 @@ public final class GuiHistoryTab {
|
|||||||
reasoningArea.setText(DETAIL_PLACEHOLDER);
|
reasoningArea.setText(DETAIL_PLACEHOLDER);
|
||||||
reasoningArea.setTooltip(new Tooltip(GuiTooltipTexts.VERLAUF_REASONING_AREA));
|
reasoningArea.setTooltip(new Tooltip(GuiTooltipTexts.VERLAUF_REASONING_AREA));
|
||||||
Label reasoningTitle = new Label("KI-Begründung (ausgewählter Versuch)");
|
Label reasoningTitle = new Label("KI-Begründung (ausgewählter Versuch)");
|
||||||
reasoningTitle.setStyle("-fx-font-weight: bold;");
|
reasoningTitle.setStyle(BOLD_STYLE);
|
||||||
|
|
||||||
VBox rightPane = new VBox(8,
|
VBox rightPane = new VBox(8,
|
||||||
detailTitle, detailGrid,
|
detailTitle, detailGrid,
|
||||||
@@ -579,7 +584,7 @@ public final class GuiHistoryTab {
|
|||||||
Path configPath = configPathSupplier.get();
|
Path configPath = configPathSupplier.get();
|
||||||
if (configPath == null) {
|
if (configPath == null) {
|
||||||
statusBarLabel.setText("Keine Konfiguration geladen – bitte zuerst eine Konfigurationsdatei öffnen.");
|
statusBarLabel.setText("Keine Konfiguration geladen – bitte zuerst eine Konfigurationsdatei öffnen.");
|
||||||
overviewTable.setPlaceholder(new Label("Keine Konfiguration geladen."));
|
overviewTable.setPlaceholder(new Label(NO_CONFIG_LOADED_MSG));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,7 +671,7 @@ public final class GuiHistoryTab {
|
|||||||
|
|
||||||
Path configPath = configPathSupplier.get();
|
Path configPath = configPathSupplier.get();
|
||||||
if (configPath == null) {
|
if (configPath == null) {
|
||||||
showInfo("Keine Konfiguration geladen.");
|
showInfo(NO_CONFIG_LOADED_MSG);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -674,28 +679,10 @@ public final class GuiHistoryTab {
|
|||||||
.filter(r -> r.overallStatus() == ProcessingStatus.SUCCESS)
|
.filter(r -> r.overallStatus() == ProcessingStatus.SUCCESS)
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append("Setzt den Status auf READY_FOR_AI zurück.\n");
|
|
||||||
sb.append("Fehlerzähler und letzter Fehlerzeitpunkt werden gelöscht.\n");
|
|
||||||
sb.append("Die Versuchshistorie bleibt vollständig erhalten.\n\n");
|
|
||||||
if (selectedItems.size() == 1) {
|
|
||||||
sb.append("Quelldatei: ").append(selectedItems.get(0).sourceFileName());
|
|
||||||
} else {
|
|
||||||
sb.append(selectedItems.size()).append(" Einträge werden zurückgesetzt.");
|
|
||||||
}
|
|
||||||
if (successCount > 0) {
|
|
||||||
sb.append("\n\nHinweis: ").append(successCount)
|
|
||||||
.append(" der ausgewählten Einträge ")
|
|
||||||
.append(successCount == 1 ? "hat" : "haben")
|
|
||||||
.append(" Status \"Erfolgreich\". ")
|
|
||||||
.append(successCount == 1 ? "Dieser Eintrag wird" : "Diese Einträge werden")
|
|
||||||
.append(" erneut verarbeitet.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
|
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
|
||||||
confirm.setTitle("Status zurücksetzen");
|
confirm.setTitle("Status zurücksetzen");
|
||||||
confirm.setHeaderText("Status zurücksetzen?");
|
confirm.setHeaderText("Status zurücksetzen?");
|
||||||
confirm.setContentText(sb.toString());
|
confirm.setContentText(buildResetConfirmationText(selectedItems, successCount));
|
||||||
Optional<ButtonType> choice = confirm.showAndWait();
|
Optional<ButtonType> choice = confirm.showAndWait();
|
||||||
if (choice.isEmpty() || choice.get() != ButtonType.OK) return;
|
if (choice.isEmpty() || choice.get() != ButtonType.OK) return;
|
||||||
|
|
||||||
@@ -729,6 +716,27 @@ public final class GuiHistoryTab {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String buildResetConfirmationText(List<DocumentHistoryRow> selectedItems, long successCount) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Setzt den Status auf READY_FOR_AI zurück.\n");
|
||||||
|
sb.append("Fehlerzähler und letzter Fehlerzeitpunkt werden gelöscht.\n");
|
||||||
|
sb.append("Die Versuchshistorie bleibt vollständig erhalten.\n\n");
|
||||||
|
if (selectedItems.size() == 1) {
|
||||||
|
sb.append("Quelldatei: ").append(selectedItems.get(0).sourceFileName());
|
||||||
|
} else {
|
||||||
|
sb.append(selectedItems.size()).append(" Einträge werden zurückgesetzt.");
|
||||||
|
}
|
||||||
|
if (successCount > 0) {
|
||||||
|
sb.append("\n\nHinweis: ").append(successCount)
|
||||||
|
.append(" der ausgewählten Einträge ")
|
||||||
|
.append(successCount == 1 ? "hat" : "haben")
|
||||||
|
.append(" Status \"Erfolgreich\". ")
|
||||||
|
.append(successCount == 1 ? "Dieser Eintrag wird" : "Diese Einträge werden")
|
||||||
|
.append(" erneut verarbeitet.");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private void handleDeleteAction() {
|
private void handleDeleteAction() {
|
||||||
if (runningCheck.getAsBoolean()) {
|
if (runningCheck.getAsBoolean()) {
|
||||||
showInfo(LAUF_AKTIV_HINWEIS);
|
showInfo(LAUF_AKTIV_HINWEIS);
|
||||||
@@ -741,7 +749,7 @@ public final class GuiHistoryTab {
|
|||||||
|
|
||||||
Path configPath = configPathSupplier.get();
|
Path configPath = configPathSupplier.get();
|
||||||
if (configPath == null) {
|
if (configPath == null) {
|
||||||
showInfo("Keine Konfiguration geladen.");
|
showInfo(NO_CONFIG_LOADED_MSG);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -818,23 +826,26 @@ public final class GuiHistoryTab {
|
|||||||
// Fehlerursache aus letztem Fehler-Versuch anzeigen
|
// Fehlerursache aus letztem Fehler-Versuch anzeigen
|
||||||
showLastFailureMessage(result.attempts(), record.overallStatus());
|
showLastFailureMessage(result.attempts(), record.overallStatus());
|
||||||
|
|
||||||
// Neuesten Versuch selektieren und Begründung anzeigen
|
selectLatestAttemptAndShowReasoning(result.attempts());
|
||||||
if (!result.attempts().isEmpty()) {
|
attemptsTable.getSelectionModel().selectedItemProperty().addListener(
|
||||||
ProcessingAttempt last = result.attempts().get(result.attempts().size() - 1);
|
(obs, old, attempt) -> onAttemptSelected(attempt));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectLatestAttemptAndShowReasoning(java.util.List<ProcessingAttempt> attempts) {
|
||||||
|
if (!attempts.isEmpty()) {
|
||||||
|
ProcessingAttempt last = attempts.get(attempts.size() - 1);
|
||||||
attemptsTable.getSelectionModel().select(last);
|
attemptsTable.getSelectionModel().select(last);
|
||||||
showReasoning(last);
|
showReasoning(last);
|
||||||
} else {
|
} else {
|
||||||
reasoningArea.setText("");
|
reasoningArea.setText("");
|
||||||
reasoningArea.setPromptText(NO_REASONING_TEXT);
|
reasoningArea.setPromptText(NO_REASONING_TEXT);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// KI-Begründung bei Versuchs-Selektion aktualisieren
|
private void onAttemptSelected(ProcessingAttempt attempt) {
|
||||||
attemptsTable.getSelectionModel().selectedItemProperty().addListener(
|
|
||||||
(obs, old, attempt) -> {
|
|
||||||
if (attempt != null) {
|
if (attempt != null) {
|
||||||
showReasoning(attempt);
|
showReasoning(attempt);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -865,7 +876,7 @@ public final class GuiHistoryTab {
|
|||||||
|
|
||||||
failureArea.setText(failureMessage != null
|
failureArea.setText(failureMessage != null
|
||||||
? AiFailureMessageTranslator.translate(failureMessage) : "");
|
? AiFailureMessageTranslator.translate(failureMessage) : "");
|
||||||
failureArea.setPromptText("Keine Fehlerdetails gespeichert.");
|
failureArea.setPromptText(NO_ERROR_DETAILS_MSG);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showReasoning(ProcessingAttempt attempt) {
|
private void showReasoning(ProcessingAttempt attempt) {
|
||||||
@@ -883,7 +894,7 @@ public final class GuiHistoryTab {
|
|||||||
clearDetailFields();
|
clearDetailFields();
|
||||||
attemptsItems.clear();
|
attemptsItems.clear();
|
||||||
failureArea.setText("");
|
failureArea.setText("");
|
||||||
failureArea.setPromptText("Keine Fehlerdetails gespeichert.");
|
failureArea.setPromptText(NO_ERROR_DETAILS_MSG);
|
||||||
reasoningArea.setText(DETAIL_PLACEHOLDER);
|
reasoningArea.setText(DETAIL_PLACEHOLDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -906,7 +917,7 @@ public final class GuiHistoryTab {
|
|||||||
|
|
||||||
private void addDetailRow(int row, String labelText, Label valueLabel) {
|
private void addDetailRow(int row, String labelText, Label valueLabel) {
|
||||||
Label label = new Label(labelText);
|
Label label = new Label(labelText);
|
||||||
label.setStyle("-fx-font-weight: bold;");
|
label.setStyle(BOLD_STYLE);
|
||||||
valueLabel.setMaxWidth(Double.MAX_VALUE);
|
valueLabel.setMaxWidth(Double.MAX_VALUE);
|
||||||
GridPane.setHgrow(valueLabel, Priority.ALWAYS);
|
GridPane.setHgrow(valueLabel, Priority.ALWAYS);
|
||||||
detailGrid.add(label, 0, row);
|
detailGrid.add(label, 0, row);
|
||||||
|
|||||||
+36
-12
@@ -119,9 +119,15 @@ class GuiBatchRunCoordinatorMiniRunTest {
|
|||||||
void startReset_invokesResetPortAndDispatchesResult() {
|
void startReset_invokesResetPortAndDispatchesResult() {
|
||||||
AtomicReference<ResetDocumentStatusResult> captured = new AtomicReference<>();
|
AtomicReference<ResetDocumentStatusResult> captured = new AtomicReference<>();
|
||||||
GuiBatchRunCoordinator.Listener listener = new GuiBatchRunCoordinator.Listener() {
|
GuiBatchRunCoordinator.Listener listener = new GuiBatchRunCoordinator.Listener() {
|
||||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
// intentionally empty
|
||||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
|
}
|
||||||
|
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override public void onResetCompleted(ResetDocumentStatusResult result) {
|
@Override public void onResetCompleted(ResetDocumentStatusResult result) {
|
||||||
captured.set(result);
|
captured.set(result);
|
||||||
}
|
}
|
||||||
@@ -170,9 +176,15 @@ class GuiBatchRunCoordinatorMiniRunTest {
|
|||||||
void startReset_portThrowsException_mapsToAllFailures() {
|
void startReset_portThrowsException_mapsToAllFailures() {
|
||||||
AtomicReference<ResetDocumentStatusResult> captured = new AtomicReference<>();
|
AtomicReference<ResetDocumentStatusResult> captured = new AtomicReference<>();
|
||||||
GuiBatchRunCoordinator.Listener listener = new GuiBatchRunCoordinator.Listener() {
|
GuiBatchRunCoordinator.Listener listener = new GuiBatchRunCoordinator.Listener() {
|
||||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
// intentionally empty
|
||||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
|
}
|
||||||
|
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override public void onResetCompleted(ResetDocumentStatusResult result) {
|
@Override public void onResetCompleted(ResetDocumentStatusResult result) {
|
||||||
captured.set(result);
|
captured.set(result);
|
||||||
}
|
}
|
||||||
@@ -198,9 +210,15 @@ class GuiBatchRunCoordinatorMiniRunTest {
|
|||||||
void listenerDefaultOnResetCompleted_doesNotThrow() {
|
void listenerDefaultOnResetCompleted_doesNotThrow() {
|
||||||
// Verify the default implementation is safe to call.
|
// Verify the default implementation is safe to call.
|
||||||
GuiBatchRunCoordinator.Listener listener = new GuiBatchRunCoordinator.Listener() {
|
GuiBatchRunCoordinator.Listener listener = new GuiBatchRunCoordinator.Listener() {
|
||||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
// intentionally empty
|
||||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
|
}
|
||||||
|
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
listener.onResetCompleted(new ResetDocumentStatusResult(0, Set.of(), Map.of()));
|
listener.onResetCompleted(new ResetDocumentStatusResult(0, Set.of(), Map.of()));
|
||||||
}
|
}
|
||||||
@@ -223,9 +241,15 @@ class GuiBatchRunCoordinatorMiniRunTest {
|
|||||||
|
|
||||||
private static GuiBatchRunCoordinator.Listener noOpListener() {
|
private static GuiBatchRunCoordinator.Listener noOpListener() {
|
||||||
return new GuiBatchRunCoordinator.Listener() {
|
return new GuiBatchRunCoordinator.Listener() {
|
||||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
// intentionally empty
|
||||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
|
}
|
||||||
|
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+21
-7
@@ -247,8 +247,12 @@ class GuiBatchRunCoordinatorTest {
|
|||||||
GuiBatchRunCoordinator coordinator = new GuiBatchRunCoordinator(
|
GuiBatchRunCoordinator coordinator = new GuiBatchRunCoordinator(
|
||||||
launcher, syncThreadFactory(), syncDispatcher(),
|
launcher, syncThreadFactory(), syncDispatcher(),
|
||||||
new GuiBatchRunCoordinator.Listener() {
|
new GuiBatchRunCoordinator.Listener() {
|
||||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||||
captured.set(outcome);
|
captured.set(outcome);
|
||||||
}
|
}
|
||||||
@@ -270,8 +274,12 @@ class GuiBatchRunCoordinatorTest {
|
|||||||
GuiBatchRunCoordinator coordinator = new GuiBatchRunCoordinator(
|
GuiBatchRunCoordinator coordinator = new GuiBatchRunCoordinator(
|
||||||
launcher, syncThreadFactory(), syncDispatcher(),
|
launcher, syncThreadFactory(), syncDispatcher(),
|
||||||
new GuiBatchRunCoordinator.Listener() {
|
new GuiBatchRunCoordinator.Listener() {
|
||||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||||
captured.set(outcome);
|
captured.set(outcome);
|
||||||
}
|
}
|
||||||
@@ -322,9 +330,15 @@ class GuiBatchRunCoordinatorTest {
|
|||||||
|
|
||||||
private static GuiBatchRunCoordinator.Listener noOpListener() {
|
private static GuiBatchRunCoordinator.Listener noOpListener() {
|
||||||
return new GuiBatchRunCoordinator.Listener() {
|
return new GuiBatchRunCoordinator.Listener() {
|
||||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
// intentionally empty
|
||||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
|
}
|
||||||
|
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-6
@@ -95,6 +95,10 @@ import de.gecheckt.pdf.umbenenner.domain.model.AiRequestRepresentation;
|
|||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class OpenAiHttpAdapter implements AiInvocationPort {
|
public class OpenAiHttpAdapter implements AiInvocationPort {
|
||||||
|
private static final String NO_CHOICE_CONTENT_SENTINEL = "NO_CHOICE_CONTENT";
|
||||||
|
private static final String JSON_KEY_CONTENT = "content";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(OpenAiHttpAdapter.class);
|
private static final Logger LOG = LogManager.getLogger(OpenAiHttpAdapter.class);
|
||||||
|
|
||||||
@@ -248,20 +252,20 @@ public class OpenAiHttpAdapter implements AiInvocationPort {
|
|||||||
JSONArray choices = json.optJSONArray("choices");
|
JSONArray choices = json.optJSONArray("choices");
|
||||||
if (choices == null || choices.isEmpty()) {
|
if (choices == null || choices.isEmpty()) {
|
||||||
LOG.warn("OpenAI response contained no choices");
|
LOG.warn("OpenAI response contained no choices");
|
||||||
return new AiInvocationTechnicalFailure(request, "NO_CHOICE_CONTENT",
|
return new AiInvocationTechnicalFailure(request, NO_CHOICE_CONTENT_SENTINEL,
|
||||||
"OpenAI response contained no choices");
|
"OpenAI response contained no choices");
|
||||||
}
|
}
|
||||||
JSONObject firstChoice = choices.getJSONObject(0);
|
JSONObject firstChoice = choices.getJSONObject(0);
|
||||||
JSONObject message = firstChoice.optJSONObject("message");
|
JSONObject message = firstChoice.optJSONObject("message");
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
LOG.warn("OpenAI response choice contained no message");
|
LOG.warn("OpenAI response choice contained no message");
|
||||||
return new AiInvocationTechnicalFailure(request, "NO_CHOICE_CONTENT",
|
return new AiInvocationTechnicalFailure(request, NO_CHOICE_CONTENT_SENTINEL,
|
||||||
"OpenAI response choice contained no message");
|
"OpenAI response choice contained no message");
|
||||||
}
|
}
|
||||||
String content = message.optString("content", null);
|
String content = message.optString(JSON_KEY_CONTENT, null);
|
||||||
if (content == null || content.isBlank()) {
|
if (content == null || content.isBlank()) {
|
||||||
LOG.warn("OpenAI response message.content is absent or blank");
|
LOG.warn("OpenAI response message.content is absent or blank");
|
||||||
return new AiInvocationTechnicalFailure(request, "NO_CHOICE_CONTENT",
|
return new AiInvocationTechnicalFailure(request, NO_CHOICE_CONTENT_SENTINEL,
|
||||||
"OpenAI response message.content is absent or blank");
|
"OpenAI response message.content is absent or blank");
|
||||||
}
|
}
|
||||||
return new AiInvocationSuccess(request, new AiRawResponse(content));
|
return new AiInvocationSuccess(request, new AiRawResponse(content));
|
||||||
@@ -347,11 +351,11 @@ public class OpenAiHttpAdapter implements AiInvocationPort {
|
|||||||
|
|
||||||
JSONObject systemMessage = new JSONObject();
|
JSONObject systemMessage = new JSONObject();
|
||||||
systemMessage.put("role", "system");
|
systemMessage.put("role", "system");
|
||||||
systemMessage.put("content", request.promptContent());
|
systemMessage.put(JSON_KEY_CONTENT, request.promptContent());
|
||||||
|
|
||||||
JSONObject userMessage = new JSONObject();
|
JSONObject userMessage = new JSONObject();
|
||||||
userMessage.put("role", "user");
|
userMessage.put("role", "user");
|
||||||
userMessage.put("content", request.documentText());
|
userMessage.put(JSON_KEY_CONTENT, request.documentText());
|
||||||
|
|
||||||
body.put("messages", new org.json.JSONArray()
|
body.put("messages", new org.json.JSONArray()
|
||||||
.put(systemMessage)
|
.put(systemMessage)
|
||||||
|
|||||||
+17
-13
@@ -46,6 +46,10 @@ import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.ModelCatalog
|
|||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class ClaudeModelCatalogAdapter implements AiModelCatalogPort {
|
public class ClaudeModelCatalogAdapter implements AiModelCatalogPort {
|
||||||
|
private static final String FAILURE_CODE_CONNECTION = "CONNECTION_FAILURE";
|
||||||
|
private static final String FAILURE_CODE_UNKNOWN = "UNKNOWN";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(ClaudeModelCatalogAdapter.class);
|
private static final Logger LOG = LogManager.getLogger(ClaudeModelCatalogAdapter.class);
|
||||||
|
|
||||||
@@ -133,28 +137,28 @@ public class ClaudeModelCatalogAdapter implements AiModelCatalogPort {
|
|||||||
|
|
||||||
} catch (java.net.http.HttpTimeoutException e) {
|
} catch (java.net.http.HttpTimeoutException e) {
|
||||||
LOG.warn("Claude model catalogue: request timed out – {}", e.getMessage());
|
LOG.warn("Claude model catalogue: request timed out – {}", e.getMessage());
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Zeitüberschreitung beim Modellabruf: " + e.getMessage());
|
"Zeitüberschreitung beim Modellabruf: " + e.getMessage());
|
||||||
} catch (java.net.ConnectException e) {
|
} catch (java.net.ConnectException e) {
|
||||||
LOG.warn("Claude model catalogue: connection failed – {}", e.getMessage());
|
LOG.warn("Claude model catalogue: connection failed – {}", e.getMessage());
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Verbindung zum Endpunkt fehlgeschlagen: " + e.getMessage());
|
"Verbindung zum Endpunkt fehlgeschlagen: " + e.getMessage());
|
||||||
} catch (java.net.UnknownHostException e) {
|
} catch (java.net.UnknownHostException e) {
|
||||||
LOG.warn("Claude model catalogue: hostname not resolvable – {}", e.getMessage());
|
LOG.warn("Claude model catalogue: hostname not resolvable – {}", e.getMessage());
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Hostname nicht auflösbar: " + e.getMessage());
|
"Hostname nicht auflösbar: " + e.getMessage());
|
||||||
} catch (java.io.IOException e) {
|
} catch (java.io.IOException e) {
|
||||||
LOG.warn("Claude model catalogue: IO error – {}", e.getMessage());
|
LOG.warn("Claude model catalogue: IO error – {}", e.getMessage());
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"E/A-Fehler beim Modellabruf: " + e.getMessage());
|
"E/A-Fehler beim Modellabruf: " + e.getMessage());
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
LOG.warn("Claude model catalogue: request interrupted");
|
LOG.warn("Claude model catalogue: request interrupted");
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Modellabruf wurde unterbrochen.");
|
"Modellabruf wurde unterbrochen.");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Claude model catalogue: unexpected error", e);
|
LOG.error("Claude model catalogue: unexpected error", e);
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "UNKNOWN",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_UNKNOWN,
|
||||||
"Unerwarteter Fehler: " + e.getMessage());
|
"Unerwarteter Fehler: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,7 +192,7 @@ public class ClaudeModelCatalogAdapter implements AiModelCatalogPort {
|
|||||||
|
|
||||||
if (status != 200) {
|
if (status != 200) {
|
||||||
LOG.warn("Claude model catalogue: unexpected HTTP status {}", status);
|
LOG.warn("Claude model catalogue: unexpected HTTP status {}", status);
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "UNKNOWN",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_UNKNOWN,
|
||||||
"Unerwarteter HTTP-Status: " + status);
|
"Unerwarteter HTTP-Status: " + status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,24 +295,24 @@ public class ClaudeModelCatalogAdapter implements AiModelCatalogPort {
|
|||||||
return handleResponse(response);
|
return handleResponse(response);
|
||||||
|
|
||||||
} catch (java.net.http.HttpTimeoutException e) {
|
} catch (java.net.http.HttpTimeoutException e) {
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Zeitüberschreitung beim Modellabruf: " + e.getMessage());
|
"Zeitüberschreitung beim Modellabruf: " + e.getMessage());
|
||||||
} catch (java.net.ConnectException e) {
|
} catch (java.net.ConnectException e) {
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Verbindung zum Endpunkt fehlgeschlagen: " + e.getMessage());
|
"Verbindung zum Endpunkt fehlgeschlagen: " + e.getMessage());
|
||||||
} catch (java.net.UnknownHostException e) {
|
} catch (java.net.UnknownHostException e) {
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Hostname nicht auflösbar: " + e.getMessage());
|
"Hostname nicht auflösbar: " + e.getMessage());
|
||||||
} catch (java.io.IOException e) {
|
} catch (java.io.IOException e) {
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"E/A-Fehler beim Modellabruf: " + e.getMessage());
|
"E/A-Fehler beim Modellabruf: " + e.getMessage());
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Modellabruf wurde unterbrochen.");
|
"Modellabruf wurde unterbrochen.");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Claude model catalogue: unexpected error", e);
|
LOG.error("Claude model catalogue: unexpected error", e);
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "UNKNOWN",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_UNKNOWN,
|
||||||
"Unerwarteter Fehler: " + e.getMessage());
|
"Unerwarteter Fehler: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-13
@@ -46,6 +46,10 @@ import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.ModelCatalog
|
|||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class OpenAiCompatibleModelCatalogAdapter implements AiModelCatalogPort {
|
public class OpenAiCompatibleModelCatalogAdapter implements AiModelCatalogPort {
|
||||||
|
private static final String FAILURE_CODE_CONNECTION = "CONNECTION_FAILURE";
|
||||||
|
private static final String FAILURE_CODE_UNKNOWN = "UNKNOWN";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(OpenAiCompatibleModelCatalogAdapter.class);
|
private static final Logger LOG = LogManager.getLogger(OpenAiCompatibleModelCatalogAdapter.class);
|
||||||
|
|
||||||
@@ -129,28 +133,28 @@ public class OpenAiCompatibleModelCatalogAdapter implements AiModelCatalogPort {
|
|||||||
|
|
||||||
} catch (java.net.http.HttpTimeoutException e) {
|
} catch (java.net.http.HttpTimeoutException e) {
|
||||||
LOG.warn("OpenAI-compatible model catalogue: request timed out – {}", e.getMessage());
|
LOG.warn("OpenAI-compatible model catalogue: request timed out – {}", e.getMessage());
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Zeitüberschreitung beim Modellabruf: " + e.getMessage());
|
"Zeitüberschreitung beim Modellabruf: " + e.getMessage());
|
||||||
} catch (java.net.ConnectException e) {
|
} catch (java.net.ConnectException e) {
|
||||||
LOG.warn("OpenAI-compatible model catalogue: connection failed – {}", e.getMessage());
|
LOG.warn("OpenAI-compatible model catalogue: connection failed – {}", e.getMessage());
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Verbindung zum Endpunkt fehlgeschlagen: " + e.getMessage());
|
"Verbindung zum Endpunkt fehlgeschlagen: " + e.getMessage());
|
||||||
} catch (java.net.UnknownHostException e) {
|
} catch (java.net.UnknownHostException e) {
|
||||||
LOG.warn("OpenAI-compatible model catalogue: hostname not resolvable – {}", e.getMessage());
|
LOG.warn("OpenAI-compatible model catalogue: hostname not resolvable – {}", e.getMessage());
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Hostname nicht auflösbar: " + e.getMessage());
|
"Hostname nicht auflösbar: " + e.getMessage());
|
||||||
} catch (java.io.IOException e) {
|
} catch (java.io.IOException e) {
|
||||||
LOG.warn("OpenAI-compatible model catalogue: IO error – {}", e.getMessage());
|
LOG.warn("OpenAI-compatible model catalogue: IO error – {}", e.getMessage());
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"E/A-Fehler beim Modellabruf: " + e.getMessage());
|
"E/A-Fehler beim Modellabruf: " + e.getMessage());
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
LOG.warn("OpenAI-compatible model catalogue: request interrupted");
|
LOG.warn("OpenAI-compatible model catalogue: request interrupted");
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Modellabruf wurde unterbrochen.");
|
"Modellabruf wurde unterbrochen.");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("OpenAI-compatible model catalogue: unexpected error", e);
|
LOG.error("OpenAI-compatible model catalogue: unexpected error", e);
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "UNKNOWN",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_UNKNOWN,
|
||||||
"Unerwarteter Fehler: " + e.getMessage());
|
"Unerwarteter Fehler: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,7 +188,7 @@ public class OpenAiCompatibleModelCatalogAdapter implements AiModelCatalogPort {
|
|||||||
|
|
||||||
if (status != 200) {
|
if (status != 200) {
|
||||||
LOG.warn("OpenAI-compatible model catalogue: unexpected HTTP status {}", status);
|
LOG.warn("OpenAI-compatible model catalogue: unexpected HTTP status {}", status);
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "UNKNOWN",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_UNKNOWN,
|
||||||
"Unerwarteter HTTP-Status: " + status);
|
"Unerwarteter HTTP-Status: " + status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,24 +289,24 @@ public class OpenAiCompatibleModelCatalogAdapter implements AiModelCatalogPort {
|
|||||||
return handleResponse(response);
|
return handleResponse(response);
|
||||||
|
|
||||||
} catch (java.net.http.HttpTimeoutException e) {
|
} catch (java.net.http.HttpTimeoutException e) {
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Zeitüberschreitung beim Modellabruf: " + e.getMessage());
|
"Zeitüberschreitung beim Modellabruf: " + e.getMessage());
|
||||||
} catch (java.net.ConnectException e) {
|
} catch (java.net.ConnectException e) {
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Verbindung zum Endpunkt fehlgeschlagen: " + e.getMessage());
|
"Verbindung zum Endpunkt fehlgeschlagen: " + e.getMessage());
|
||||||
} catch (java.net.UnknownHostException e) {
|
} catch (java.net.UnknownHostException e) {
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Hostname nicht auflösbar: " + e.getMessage());
|
"Hostname nicht auflösbar: " + e.getMessage());
|
||||||
} catch (java.io.IOException e) {
|
} catch (java.io.IOException e) {
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"E/A-Fehler beim Modellabruf: " + e.getMessage());
|
"E/A-Fehler beim Modellabruf: " + e.getMessage());
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "CONNECTION_FAILURE",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_CONNECTION,
|
||||||
"Modellabruf wurde unterbrochen.");
|
"Modellabruf wurde unterbrochen.");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("OpenAI-compatible model catalogue: unexpected error", e);
|
LOG.error("OpenAI-compatible model catalogue: unexpected error", e);
|
||||||
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, "UNKNOWN",
|
return new ModelCatalogResult.TechnicalFailure(PROVIDER_ID, FAILURE_CODE_UNKNOWN,
|
||||||
"Unerwarteter Fehler: " + e.getMessage());
|
"Unerwarteter Fehler: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -48,6 +48,8 @@ import de.gecheckt.pdf.umbenenner.domain.model.PromptIdentifier;
|
|||||||
* werden propagiert.
|
* werden propagiert.
|
||||||
*/
|
*/
|
||||||
public class FilesystemPromptPortAdapter implements PromptPort {
|
public class FilesystemPromptPortAdapter implements PromptPort {
|
||||||
|
private static final String SAVE_FAILED_LOG_MSG = "Prompt speichern fehlgeschlagen: {}";
|
||||||
|
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(FilesystemPromptPortAdapter.class);
|
private static final Logger LOG = LogManager.getLogger(FilesystemPromptPortAdapter.class);
|
||||||
|
|
||||||
@@ -125,7 +127,7 @@ public class FilesystemPromptPortAdapter implements PromptPort {
|
|||||||
if (targetDir == null || !Files.isDirectory(targetDir)) {
|
if (targetDir == null || !Files.isDirectory(targetDir)) {
|
||||||
String message = "Zielordner der Prompt-Datei existiert nicht: "
|
String message = "Zielordner der Prompt-Datei existiert nicht: "
|
||||||
+ (targetDir != null ? targetDir.toAbsolutePath() : "unbekannt");
|
+ (targetDir != null ? targetDir.toAbsolutePath() : "unbekannt");
|
||||||
LOG.warn("Prompt speichern fehlgeschlagen: {}", message);
|
LOG.warn(SAVE_FAILED_LOG_MSG, message);
|
||||||
return new PromptSaveResult.TargetDirectoryMissing(message);
|
return new PromptSaveResult.TargetDirectoryMissing(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +140,7 @@ public class FilesystemPromptPortAdapter implements PromptPort {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
beräumeTempDatei(tempFile);
|
beräumeTempDatei(tempFile);
|
||||||
String message = "Fehler beim Schreiben der temporären Prompt-Datei: " + e.getMessage();
|
String message = "Fehler beim Schreiben der temporären Prompt-Datei: " + e.getMessage();
|
||||||
LOG.warn("Prompt speichern fehlgeschlagen: {}", message, e);
|
LOG.warn(SAVE_FAILED_LOG_MSG, message, e);
|
||||||
return new PromptSaveResult.WriteFailed(message, e);
|
return new PromptSaveResult.WriteFailed(message, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +157,7 @@ public class FilesystemPromptPortAdapter implements PromptPort {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
beräumeTempDatei(tempFile);
|
beräumeTempDatei(tempFile);
|
||||||
String message = "Fehler beim atomaren Verschieben der Prompt-Datei: " + e.getMessage();
|
String message = "Fehler beim atomaren Verschieben der Prompt-Datei: " + e.getMessage();
|
||||||
LOG.warn("Prompt speichern fehlgeschlagen: {}", message, e);
|
LOG.warn(SAVE_FAILED_LOG_MSG, message, e);
|
||||||
return new PromptSaveResult.AtomicMoveFailed(message);
|
return new PromptSaveResult.AtomicMoveFailed(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -43,6 +43,8 @@ import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceC
|
|||||||
* Ausnahmen an den Aufrufer weitergegeben.
|
* Ausnahmen an den Aufrufer weitergegeben.
|
||||||
*/
|
*/
|
||||||
public class FilesystemResourceCreationAdapter implements ResourceCreationPort {
|
public class FilesystemResourceCreationAdapter implements ResourceCreationPort {
|
||||||
|
private static final String INVALID_PATH_PREFIX = "Ungültiger Pfad: ";
|
||||||
|
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(FilesystemResourceCreationAdapter.class);
|
private static final Logger LOG = LogManager.getLogger(FilesystemResourceCreationAdapter.class);
|
||||||
|
|
||||||
@@ -66,7 +68,7 @@ public class FilesystemResourceCreationAdapter implements ResourceCreationPort {
|
|||||||
public CorrectionOutcome createDirectory(CorrectionSuggestion.CreateDirectory suggestion) {
|
public CorrectionOutcome createDirectory(CorrectionSuggestion.CreateDirectory suggestion) {
|
||||||
Path path = toPath(suggestion.path());
|
Path path = toPath(suggestion.path());
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
String msg = "Ungültiger Pfad: " + suggestion.path();
|
String msg = INVALID_PATH_PREFIX + suggestion.path();
|
||||||
LOG.warn("Ordner anlegen fehlgeschlagen: {}", msg);
|
LOG.warn("Ordner anlegen fehlgeschlagen: {}", msg);
|
||||||
return new CorrectionOutcome.Failed(suggestion, msg);
|
return new CorrectionOutcome.Failed(suggestion, msg);
|
||||||
}
|
}
|
||||||
@@ -114,7 +116,7 @@ public class FilesystemResourceCreationAdapter implements ResourceCreationPort {
|
|||||||
public CorrectionOutcome createPromptFile(CorrectionSuggestion.CreatePromptFile suggestion) {
|
public CorrectionOutcome createPromptFile(CorrectionSuggestion.CreatePromptFile suggestion) {
|
||||||
Path path = toPath(suggestion.path());
|
Path path = toPath(suggestion.path());
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
String msg = "Ungültiger Pfad: " + suggestion.path();
|
String msg = INVALID_PATH_PREFIX + suggestion.path();
|
||||||
LOG.warn("Prompt-Datei erzeugen fehlgeschlagen: {}", msg);
|
LOG.warn("Prompt-Datei erzeugen fehlgeschlagen: {}", msg);
|
||||||
return new CorrectionOutcome.Failed(suggestion, msg);
|
return new CorrectionOutcome.Failed(suggestion, msg);
|
||||||
}
|
}
|
||||||
@@ -164,7 +166,7 @@ public class FilesystemResourceCreationAdapter implements ResourceCreationPort {
|
|||||||
public CorrectionOutcome prepareSqlitePath(CorrectionSuggestion.PrepareSqlitePath suggestion) {
|
public CorrectionOutcome prepareSqlitePath(CorrectionSuggestion.PrepareSqlitePath suggestion) {
|
||||||
Path path = toPath(suggestion.path());
|
Path path = toPath(suggestion.path());
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
String msg = "Ungültiger Pfad: " + suggestion.path();
|
String msg = INVALID_PATH_PREFIX + suggestion.path();
|
||||||
LOG.warn("SQLite-Pfad vorbereiten fehlgeschlagen: {}", msg);
|
LOG.warn("SQLite-Pfad vorbereiten fehlgeschlagen: {}", msg);
|
||||||
return new CorrectionOutcome.Failed(suggestion, msg);
|
return new CorrectionOutcome.Failed(suggestion, msg);
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-4
@@ -43,6 +43,8 @@ import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
|||||||
* application/domain type.
|
* application/domain type.
|
||||||
*/
|
*/
|
||||||
public class SqliteProcessingAttemptRepositoryAdapter implements ProcessingAttemptRepository {
|
public class SqliteProcessingAttemptRepositoryAdapter implements ProcessingAttemptRepository {
|
||||||
|
private static final String FINGERPRINT_NOT_NULL = "fingerprint must not be null";
|
||||||
|
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger(SqliteProcessingAttemptRepositoryAdapter.class);
|
private static final Logger logger = LogManager.getLogger(SqliteProcessingAttemptRepositoryAdapter.class);
|
||||||
|
|
||||||
@@ -78,7 +80,7 @@ public class SqliteProcessingAttemptRepositoryAdapter implements ProcessingAttem
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int loadNextAttemptNumber(DocumentFingerprint fingerprint) {
|
public int loadNextAttemptNumber(DocumentFingerprint fingerprint) {
|
||||||
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
Objects.requireNonNull(fingerprint, FINGERPRINT_NOT_NULL);
|
||||||
|
|
||||||
String sql = """
|
String sql = """
|
||||||
SELECT COALESCE(MAX(attempt_number), 0) + 1 AS next_attempt_number
|
SELECT COALESCE(MAX(attempt_number), 0) + 1 AS next_attempt_number
|
||||||
@@ -204,7 +206,7 @@ public class SqliteProcessingAttemptRepositoryAdapter implements ProcessingAttem
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<ProcessingAttempt> findAllByFingerprint(DocumentFingerprint fingerprint) {
|
public List<ProcessingAttempt> findAllByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
Objects.requireNonNull(fingerprint, FINGERPRINT_NOT_NULL);
|
||||||
|
|
||||||
String sql = """
|
String sql = """
|
||||||
SELECT
|
SELECT
|
||||||
@@ -255,7 +257,7 @@ public class SqliteProcessingAttemptRepositoryAdapter implements ProcessingAttem
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ProcessingAttempt findLatestProposalReadyAttempt(DocumentFingerprint fingerprint) {
|
public ProcessingAttempt findLatestProposalReadyAttempt(DocumentFingerprint fingerprint) {
|
||||||
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
Objects.requireNonNull(fingerprint, FINGERPRINT_NOT_NULL);
|
||||||
|
|
||||||
String sql = """
|
String sql = """
|
||||||
SELECT
|
SELECT
|
||||||
@@ -422,7 +424,7 @@ public class SqliteProcessingAttemptRepositoryAdapter implements ProcessingAttem
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void deleteAllByFingerprint(DocumentFingerprint fingerprint) {
|
public void deleteAllByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
Objects.requireNonNull(fingerprint, FINGERPRINT_NOT_NULL);
|
||||||
|
|
||||||
String sql = "DELETE FROM processing_attempt WHERE fingerprint = ?";
|
String sql = "DELETE FROM processing_attempt WHERE fingerprint = ?";
|
||||||
|
|
||||||
|
|||||||
+24
-19
@@ -62,6 +62,11 @@ import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitiali
|
|||||||
* Domain-/Application-Typen erscheinen keine JDBC- oder SQLite-Typen.
|
* Domain-/Application-Typen erscheinen keine JDBC- oder SQLite-Typen.
|
||||||
*/
|
*/
|
||||||
public class SqliteSchemaInitializationAdapter implements PersistenceSchemaInitializationPort {
|
public class SqliteSchemaInitializationAdapter implements PersistenceSchemaInitializationPort {
|
||||||
|
private static final String TABLE_DOCUMENT_RECORD = "document_record";
|
||||||
|
private static final String TABLE_PROCESSING_ATTEMPT = "processing_attempt";
|
||||||
|
private static final String COL_FINGERPRINT = "fingerprint";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger(SqliteSchemaInitializationAdapter.class);
|
private static final Logger logger = LogManager.getLogger(SqliteSchemaInitializationAdapter.class);
|
||||||
|
|
||||||
@@ -71,7 +76,7 @@ public class SqliteSchemaInitializationAdapter implements PersistenceSchemaIniti
|
|||||||
|
|
||||||
/** Alle erwarteten Spalten der Tabelle {@code document_record}. */
|
/** Alle erwarteten Spalten der Tabelle {@code document_record}. */
|
||||||
private static final Set<String> EXPECTED_COLUMNS_DOCUMENT_RECORD = Set.of(
|
private static final Set<String> EXPECTED_COLUMNS_DOCUMENT_RECORD = Set.of(
|
||||||
"id", "fingerprint", "last_known_source_locator", "last_known_source_file_name",
|
"id", COL_FINGERPRINT, "last_known_source_locator", "last_known_source_file_name",
|
||||||
"overall_status", "content_error_count", "transient_error_count",
|
"overall_status", "content_error_count", "transient_error_count",
|
||||||
"last_failure_instant", "last_success_instant", "created_at", "updated_at",
|
"last_failure_instant", "last_success_instant", "created_at", "updated_at",
|
||||||
"last_target_path", "last_target_file_name"
|
"last_target_path", "last_target_file_name"
|
||||||
@@ -79,7 +84,7 @@ public class SqliteSchemaInitializationAdapter implements PersistenceSchemaIniti
|
|||||||
|
|
||||||
/** Alle erwarteten Spalten der Tabelle {@code processing_attempt}. */
|
/** Alle erwarteten Spalten der Tabelle {@code processing_attempt}. */
|
||||||
private static final Set<String> EXPECTED_COLUMNS_PROCESSING_ATTEMPT = Set.of(
|
private static final Set<String> EXPECTED_COLUMNS_PROCESSING_ATTEMPT = Set.of(
|
||||||
"id", "fingerprint", "run_id", "attempt_number", "started_at", "ended_at",
|
"id", COL_FINGERPRINT, "run_id", "attempt_number", "started_at", "ended_at",
|
||||||
"status", "failure_class", "failure_message", "retryable",
|
"status", "failure_class", "failure_message", "retryable",
|
||||||
"model_name", "prompt_identifier", "processed_page_count", "sent_character_count",
|
"model_name", "prompt_identifier", "processed_page_count", "sent_character_count",
|
||||||
"ai_raw_response", "ai_reasoning", "resolved_date", "date_source",
|
"ai_raw_response", "ai_reasoning", "resolved_date", "date_source",
|
||||||
@@ -286,8 +291,8 @@ public class SqliteSchemaInitializationAdapter implements PersistenceSchemaIniti
|
|||||||
return DbState.FLYWAY_MANAGED;
|
return DbState.FLYWAY_MANAGED;
|
||||||
}
|
}
|
||||||
// "Leer" = keine Tabellen vorhanden (unabhängig von Dateigröße)
|
// "Leer" = keine Tabellen vorhanden (unabhängig von Dateigröße)
|
||||||
boolean hasFachlicheTabellen = tables.contains("document_record")
|
boolean hasFachlicheTabellen = tables.contains(TABLE_DOCUMENT_RECORD)
|
||||||
|| tables.contains("processing_attempt");
|
|| tables.contains(TABLE_PROCESSING_ATTEMPT);
|
||||||
if (hasFachlicheTabellen) {
|
if (hasFachlicheTabellen) {
|
||||||
return DbState.EXISTING_WITHOUT_FLYWAY;
|
return DbState.EXISTING_WITHOUT_FLYWAY;
|
||||||
}
|
}
|
||||||
@@ -320,25 +325,25 @@ public class SqliteSchemaInitializationAdapter implements PersistenceSchemaIniti
|
|||||||
|
|
||||||
// Tabellen prüfen
|
// Tabellen prüfen
|
||||||
Set<String> tabellen = readTableNames(meta);
|
Set<String> tabellen = readTableNames(meta);
|
||||||
if (!tabellen.contains("document_record")) {
|
if (!tabellen.contains(TABLE_DOCUMENT_RECORD)) {
|
||||||
fehler.add("Tabelle 'document_record' fehlt");
|
fehler.add("Tabelle 'document_record' fehlt");
|
||||||
}
|
}
|
||||||
if (!tabellen.contains("processing_attempt")) {
|
if (!tabellen.contains(TABLE_PROCESSING_ATTEMPT)) {
|
||||||
fehler.add("Tabelle 'processing_attempt' fehlt");
|
fehler.add("Tabelle 'processing_attempt' fehlt");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spalten prüfen – nur wenn Tabellen vorhanden
|
// Spalten prüfen – nur wenn Tabellen vorhanden
|
||||||
if (tabellen.contains("document_record")) {
|
if (tabellen.contains(TABLE_DOCUMENT_RECORD)) {
|
||||||
pruefeSpaltenvollstaendigkeit(meta, "document_record",
|
pruefeSpaltenvollstaendigkeit(meta, TABLE_DOCUMENT_RECORD,
|
||||||
EXPECTED_COLUMNS_DOCUMENT_RECORD, fehler);
|
EXPECTED_COLUMNS_DOCUMENT_RECORD, fehler);
|
||||||
}
|
}
|
||||||
if (tabellen.contains("processing_attempt")) {
|
if (tabellen.contains(TABLE_PROCESSING_ATTEMPT)) {
|
||||||
pruefeSpaltenvollstaendigkeit(meta, "processing_attempt",
|
pruefeSpaltenvollstaendigkeit(meta, TABLE_PROCESSING_ATTEMPT,
|
||||||
EXPECTED_COLUMNS_PROCESSING_ATTEMPT, fehler);
|
EXPECTED_COLUMNS_PROCESSING_ATTEMPT, fehler);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indizes prüfen
|
// Indizes prüfen
|
||||||
if (tabellen.contains("document_record") && tabellen.contains("processing_attempt")) {
|
if (tabellen.contains(TABLE_DOCUMENT_RECORD) && tabellen.contains(TABLE_PROCESSING_ATTEMPT)) {
|
||||||
Set<String> vorhandeneIndizes = readIndexNames(meta);
|
Set<String> vorhandeneIndizes = readIndexNames(meta);
|
||||||
for (String erwartetIndex : EXPECTED_INDEXES) {
|
for (String erwartetIndex : EXPECTED_INDEXES) {
|
||||||
if (!vorhandeneIndizes.contains(erwartetIndex)) {
|
if (!vorhandeneIndizes.contains(erwartetIndex)) {
|
||||||
@@ -348,10 +353,10 @@ public class SqliteSchemaInitializationAdapter implements PersistenceSchemaIniti
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Constraints prüfen (soweit per Metadata prüfbar)
|
// Constraints prüfen (soweit per Metadata prüfbar)
|
||||||
if (tabellen.contains("document_record")) {
|
if (tabellen.contains(TABLE_DOCUMENT_RECORD)) {
|
||||||
pruefeUniqueConstraintAufFingerprint(conn, fehler);
|
pruefeUniqueConstraintAufFingerprint(conn, fehler);
|
||||||
}
|
}
|
||||||
if (tabellen.contains("processing_attempt")) {
|
if (tabellen.contains(TABLE_PROCESSING_ATTEMPT)) {
|
||||||
pruefeForeignKeyAufDocumentRecord(conn, fehler);
|
pruefeForeignKeyAufDocumentRecord(conn, fehler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,10 +404,10 @@ public class SqliteSchemaInitializationAdapter implements PersistenceSchemaIniti
|
|||||||
private void pruefeUniqueConstraintAufFingerprint(Connection conn,
|
private void pruefeUniqueConstraintAufFingerprint(Connection conn,
|
||||||
List<String> fehler) throws SQLException {
|
List<String> fehler) throws SQLException {
|
||||||
boolean uniqueGefunden = false;
|
boolean uniqueGefunden = false;
|
||||||
try (ResultSet rs = conn.getMetaData().getIndexInfo(null, null, "document_record", true, false)) {
|
try (ResultSet rs = conn.getMetaData().getIndexInfo(null, null, TABLE_DOCUMENT_RECORD, true, false)) {
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
String spalte = rs.getString("COLUMN_NAME");
|
String spalte = rs.getString("COLUMN_NAME");
|
||||||
if ("fingerprint".equalsIgnoreCase(spalte)) {
|
if (COL_FINGERPRINT.equalsIgnoreCase(spalte)) {
|
||||||
uniqueGefunden = true;
|
uniqueGefunden = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -424,12 +429,12 @@ public class SqliteSchemaInitializationAdapter implements PersistenceSchemaIniti
|
|||||||
private void pruefeForeignKeyAufDocumentRecord(Connection conn,
|
private void pruefeForeignKeyAufDocumentRecord(Connection conn,
|
||||||
List<String> fehler) throws SQLException {
|
List<String> fehler) throws SQLException {
|
||||||
boolean fkGefunden = false;
|
boolean fkGefunden = false;
|
||||||
try (ResultSet rs = conn.getMetaData().getImportedKeys(null, null, "processing_attempt")) {
|
try (ResultSet rs = conn.getMetaData().getImportedKeys(null, null, TABLE_PROCESSING_ATTEMPT)) {
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
String pkTabelle = rs.getString("PKTABLE_NAME");
|
String pkTabelle = rs.getString("PKTABLE_NAME");
|
||||||
String fkSpalte = rs.getString("FKCOLUMN_NAME");
|
String fkSpalte = rs.getString("FKCOLUMN_NAME");
|
||||||
if ("document_record".equalsIgnoreCase(pkTabelle)
|
if (TABLE_DOCUMENT_RECORD.equalsIgnoreCase(pkTabelle)
|
||||||
&& "fingerprint".equalsIgnoreCase(fkSpalte)) {
|
&& COL_FINGERPRINT.equalsIgnoreCase(fkSpalte)) {
|
||||||
fkGefunden = true;
|
fkGefunden = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -561,7 +566,7 @@ public class SqliteSchemaInitializationAdapter implements PersistenceSchemaIniti
|
|||||||
*/
|
*/
|
||||||
private static Set<String> readIndexNames(DatabaseMetaData meta) throws SQLException {
|
private static Set<String> readIndexNames(DatabaseMetaData meta) throws SQLException {
|
||||||
Set<String> names = new HashSet<>();
|
Set<String> names = new HashSet<>();
|
||||||
for (String tabelle : new String[]{"document_record", "processing_attempt"}) {
|
for (String tabelle : new String[]{TABLE_DOCUMENT_RECORD, TABLE_PROCESSING_ATTEMPT}) {
|
||||||
try (ResultSet rs = meta.getIndexInfo(null, null, tabelle, false, false)) {
|
try (ResultSet rs = meta.getIndexInfo(null, null, tabelle, false, false)) {
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
String indexName = rs.getString("INDEX_NAME");
|
String indexName = rs.getString("INDEX_NAME");
|
||||||
|
|||||||
+5
-3
@@ -24,6 +24,8 @@ import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
|||||||
* and processing attempt repositories.
|
* and processing attempt repositories.
|
||||||
*/
|
*/
|
||||||
public class SqliteUnitOfWorkAdapter implements UnitOfWorkPort {
|
public class SqliteUnitOfWorkAdapter implements UnitOfWorkPort {
|
||||||
|
private static final String ROLLBACK_FAILED_MSG = "Rollback fehlgeschlagen: {}";
|
||||||
|
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger(SqliteUnitOfWorkAdapter.class);
|
private static final Logger logger = LogManager.getLogger(SqliteUnitOfWorkAdapter.class);
|
||||||
|
|
||||||
@@ -57,7 +59,7 @@ public class SqliteUnitOfWorkAdapter implements UnitOfWorkPort {
|
|||||||
connection.rollback();
|
connection.rollback();
|
||||||
logger.debug("Transaktion zurückgerollt (Dokumentfehler): {}", e.getMessage());
|
logger.debug("Transaktion zurückgerollt (Dokumentfehler): {}", e.getMessage());
|
||||||
} catch (SQLException rollbackEx) {
|
} catch (SQLException rollbackEx) {
|
||||||
logger.error("Rollback fehlgeschlagen: {}", rollbackEx.getMessage(), rollbackEx);
|
logger.error(ROLLBACK_FAILED_MSG, rollbackEx.getMessage(), rollbackEx);
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
@@ -66,7 +68,7 @@ public class SqliteUnitOfWorkAdapter implements UnitOfWorkPort {
|
|||||||
connection.rollback();
|
connection.rollback();
|
||||||
logger.debug("Transaktion zurückgerollt (Laufzeitfehler): {}", e.getMessage());
|
logger.debug("Transaktion zurückgerollt (Laufzeitfehler): {}", e.getMessage());
|
||||||
} catch (SQLException rollbackEx) {
|
} catch (SQLException rollbackEx) {
|
||||||
logger.error("Rollback fehlgeschlagen: {}", rollbackEx.getMessage(), rollbackEx);
|
logger.error(ROLLBACK_FAILED_MSG, rollbackEx.getMessage(), rollbackEx);
|
||||||
}
|
}
|
||||||
throw new DocumentPersistenceException("Transaktion fehlgeschlagen: " + e.getMessage(), e);
|
throw new DocumentPersistenceException("Transaktion fehlgeschlagen: " + e.getMessage(), e);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
@@ -75,7 +77,7 @@ public class SqliteUnitOfWorkAdapter implements UnitOfWorkPort {
|
|||||||
connection.rollback();
|
connection.rollback();
|
||||||
logger.debug("Transaktion zurückgerollt (SQL-Fehler): {}", e.getMessage());
|
logger.debug("Transaktion zurückgerollt (SQL-Fehler): {}", e.getMessage());
|
||||||
} catch (SQLException rollbackEx) {
|
} catch (SQLException rollbackEx) {
|
||||||
logger.error("Rollback fehlgeschlagen: {}", rollbackEx.getMessage(), rollbackEx);
|
logger.error(ROLLBACK_FAILED_MSG, rollbackEx.getMessage(), rollbackEx);
|
||||||
}
|
}
|
||||||
throw new DocumentPersistenceException("Transaktion fehlgeschlagen: " + e.getMessage(), e);
|
throw new DocumentPersistenceException("Transaktion fehlgeschlagen: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-5
@@ -214,10 +214,20 @@ class AnthropicClaudeAdapterIntegrationTest {
|
|||||||
* where log output is not relevant to the assertion.
|
* where log output is not relevant to the assertion.
|
||||||
*/
|
*/
|
||||||
private static class NoOpProcessingLogger implements ProcessingLogger {
|
private static class NoOpProcessingLogger implements ProcessingLogger {
|
||||||
@Override public void info(String message, Object... args) {}
|
@Override public void info(String message, Object... args) {
|
||||||
@Override public void debug(String message, Object... args) {}
|
// intentionally empty
|
||||||
@Override public void warn(String message, Object... args) {}
|
}
|
||||||
@Override public void error(String message, Object... args) {}
|
@Override public void debug(String message, Object... args) {
|
||||||
@Override public void debugSensitiveAiContent(String message, Object... args) {}
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void warn(String message, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void error(String message, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void debugSensitiveAiContent(String message, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-3
@@ -1,6 +1,7 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -192,12 +193,14 @@ class SqliteSchemaInitializationAdapterTest {
|
|||||||
String jdbcUrl = jdbcUrl(dir, "fall3.db");
|
String jdbcUrl = jdbcUrl(dir, "fall3.db");
|
||||||
SqliteSchemaInitializationAdapter adapter = new SqliteSchemaInitializationAdapter(jdbcUrl);
|
SqliteSchemaInitializationAdapter adapter = new SqliteSchemaInitializationAdapter(jdbcUrl);
|
||||||
|
|
||||||
|
assertThatCode(() -> {
|
||||||
// Erster Aufruf (Fall 1)
|
// Erster Aufruf (Fall 1)
|
||||||
adapter.initializeSchema();
|
adapter.initializeSchema();
|
||||||
// Zweiter Aufruf (Fall 3) – darf nicht werfen
|
// Zweiter Aufruf (Fall 3) – darf nicht werfen
|
||||||
adapter.initializeSchema();
|
adapter.initializeSchema();
|
||||||
// Dritter Aufruf (Fall 3) – ebenfalls idempotent
|
// Dritter Aufruf (Fall 3) – ebenfalls idempotent
|
||||||
adapter.initializeSchema();
|
adapter.initializeSchema();
|
||||||
|
}).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -253,7 +256,12 @@ class SqliteSchemaInitializationAdapterTest {
|
|||||||
ds.setUrl(jdbcUrl);
|
ds.setUrl(jdbcUrl);
|
||||||
|
|
||||||
try (Connection conn = ds.getConnection()) {
|
try (Connection conn = ds.getConnection()) {
|
||||||
assertThatThrownBy(() -> {
|
assertThatThrownBy(() -> insertOrphanedProcessingAttempt(conn))
|
||||||
|
.isInstanceOf(SQLException.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void insertOrphanedProcessingAttempt(Connection conn) throws SQLException {
|
||||||
try (var ps = conn.prepareStatement("""
|
try (var ps = conn.prepareStatement("""
|
||||||
INSERT INTO processing_attempt
|
INSERT INTO processing_attempt
|
||||||
(fingerprint, run_id, attempt_number, started_at, ended_at, status, retryable)
|
(fingerprint, run_id, attempt_number, started_at, ended_at, status, retryable)
|
||||||
@@ -262,8 +270,6 @@ class SqliteSchemaInitializationAdapterTest {
|
|||||||
""")) {
|
""")) {
|
||||||
ps.executeUpdate();
|
ps.executeUpdate();
|
||||||
}
|
}
|
||||||
}).isInstanceOf(SQLException.class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.adapter.out.targetfolder;
|
package de.gecheckt.pdf.umbenenner.adapter.out.targetfolder;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
|
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -219,8 +220,8 @@ class FilesystemTargetFolderAdapterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void tryDeleteTargetFile_fileDoesNotExist_doesNotThrow() {
|
void tryDeleteTargetFile_fileDoesNotExist_doesNotThrow() {
|
||||||
// Must not throw even if the file is absent
|
assertThatCode(() -> adapter.tryDeleteTargetFile("nonexistent.pdf"))
|
||||||
adapter.tryDeleteTargetFile("nonexistent.pdf");
|
.doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
+5
-3
@@ -18,6 +18,8 @@ public sealed interface PromptSaveResult
|
|||||||
PromptSaveResult.WriteFailed,
|
PromptSaveResult.WriteFailed,
|
||||||
PromptSaveResult.TargetDirectoryMissing,
|
PromptSaveResult.TargetDirectoryMissing,
|
||||||
PromptSaveResult.AtomicMoveFailed {
|
PromptSaveResult.AtomicMoveFailed {
|
||||||
|
String MESSAGE_NOT_NULL = "message must not be null";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Prompt-Datei wurde erfolgreich gespeichert.
|
* Die Prompt-Datei wurde erfolgreich gespeichert.
|
||||||
@@ -53,7 +55,7 @@ public sealed interface PromptSaveResult
|
|||||||
* @throws NullPointerException wenn {@code message} null ist
|
* @throws NullPointerException wenn {@code message} null ist
|
||||||
*/
|
*/
|
||||||
public WriteFailed {
|
public WriteFailed {
|
||||||
java.util.Objects.requireNonNull(message, "message must not be null");
|
java.util.Objects.requireNonNull(message, MESSAGE_NOT_NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ public sealed interface PromptSaveResult
|
|||||||
* @throws NullPointerException wenn {@code message} null ist
|
* @throws NullPointerException wenn {@code message} null ist
|
||||||
*/
|
*/
|
||||||
public TargetDirectoryMissing {
|
public TargetDirectoryMissing {
|
||||||
java.util.Objects.requireNonNull(message, "message must not be null");
|
java.util.Objects.requireNonNull(message, MESSAGE_NOT_NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +92,7 @@ public sealed interface PromptSaveResult
|
|||||||
* @throws NullPointerException wenn {@code message} null ist
|
* @throws NullPointerException wenn {@code message} null ist
|
||||||
*/
|
*/
|
||||||
public AtomicMoveFailed {
|
public AtomicMoveFailed {
|
||||||
java.util.Objects.requireNonNull(message, "message must not be null");
|
java.util.Objects.requireNonNull(message, MESSAGE_NOT_NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-4
@@ -29,6 +29,8 @@ public sealed interface ModelCatalogResult
|
|||||||
ModelCatalogResult.EmptyList,
|
ModelCatalogResult.EmptyList,
|
||||||
ModelCatalogResult.IncompleteConfiguration,
|
ModelCatalogResult.IncompleteConfiguration,
|
||||||
ModelCatalogResult.TechnicalFailure {
|
ModelCatalogResult.TechnicalFailure {
|
||||||
|
String PROVIDER_ID_NOT_NULL = "providerIdentifier must not be null";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The provider returned a non-empty list of available model identifiers.
|
* The provider returned a non-empty list of available model identifiers.
|
||||||
@@ -55,7 +57,7 @@ public sealed interface ModelCatalogResult
|
|||||||
* @throws IllegalArgumentException if {@code models} is empty
|
* @throws IllegalArgumentException if {@code models} is empty
|
||||||
*/
|
*/
|
||||||
public Success {
|
public Success {
|
||||||
Objects.requireNonNull(providerIdentifier, "providerIdentifier must not be null");
|
Objects.requireNonNull(providerIdentifier, PROVIDER_ID_NOT_NULL);
|
||||||
Objects.requireNonNull(models, "models must not be null");
|
Objects.requireNonNull(models, "models must not be null");
|
||||||
Objects.requireNonNull(loadedAt, "loadedAt must not be null");
|
Objects.requireNonNull(loadedAt, "loadedAt must not be null");
|
||||||
if (models.isEmpty()) {
|
if (models.isEmpty()) {
|
||||||
@@ -88,7 +90,7 @@ public sealed interface ModelCatalogResult
|
|||||||
* @throws NullPointerException if any parameter is {@code null}
|
* @throws NullPointerException if any parameter is {@code null}
|
||||||
*/
|
*/
|
||||||
public EmptyList {
|
public EmptyList {
|
||||||
Objects.requireNonNull(providerIdentifier, "providerIdentifier must not be null");
|
Objects.requireNonNull(providerIdentifier, PROVIDER_ID_NOT_NULL);
|
||||||
Objects.requireNonNull(loadedAt, "loadedAt must not be null");
|
Objects.requireNonNull(loadedAt, "loadedAt must not be null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +120,7 @@ public sealed interface ModelCatalogResult
|
|||||||
* @throws NullPointerException if any parameter is {@code null}
|
* @throws NullPointerException if any parameter is {@code null}
|
||||||
*/
|
*/
|
||||||
public IncompleteConfiguration {
|
public IncompleteConfiguration {
|
||||||
Objects.requireNonNull(providerIdentifier, "providerIdentifier must not be null");
|
Objects.requireNonNull(providerIdentifier, PROVIDER_ID_NOT_NULL);
|
||||||
Objects.requireNonNull(missingReason, "missingReason must not be null");
|
Objects.requireNonNull(missingReason, "missingReason must not be null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +155,7 @@ public sealed interface ModelCatalogResult
|
|||||||
* @throws NullPointerException if any parameter is {@code null}
|
* @throws NullPointerException if any parameter is {@code null}
|
||||||
*/
|
*/
|
||||||
public TechnicalFailure {
|
public TechnicalFailure {
|
||||||
Objects.requireNonNull(providerIdentifier, "providerIdentifier must not be null");
|
Objects.requireNonNull(providerIdentifier, PROVIDER_ID_NOT_NULL);
|
||||||
Objects.requireNonNull(errorCategory, "errorCategory must not be null");
|
Objects.requireNonNull(errorCategory, "errorCategory must not be null");
|
||||||
Objects.requireNonNull(errorDetail, "errorDetail must not be null");
|
Objects.requireNonNull(errorDetail, "errorDetail must not be null");
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-4
@@ -38,6 +38,10 @@ import de.gecheckt.pdf.umbenenner.domain.model.ParsedAiResponse;
|
|||||||
* of {@link AiResponseValidator}.
|
* of {@link AiResponseValidator}.
|
||||||
*/
|
*/
|
||||||
public final class AiResponseParser {
|
public final class AiResponseParser {
|
||||||
|
private static final String JSON_KEY_TITLE = "title";
|
||||||
|
private static final String JSON_KEY_REASONING = "reasoning";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private AiResponseParser() {
|
private AiResponseParser() {
|
||||||
// Static utility – no instances
|
// Static utility – no instances
|
||||||
@@ -81,19 +85,19 @@ public final class AiResponseParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate mandatory field: title
|
// Validate mandatory field: title
|
||||||
if (!json.has("title") || json.isNull("title")) {
|
if (!json.has(JSON_KEY_TITLE) || json.isNull(JSON_KEY_TITLE)) {
|
||||||
return new AiResponseParsingFailure("MISSING_TITLE", "AI response missing mandatory field 'title'");
|
return new AiResponseParsingFailure("MISSING_TITLE", "AI response missing mandatory field 'title'");
|
||||||
}
|
}
|
||||||
String title = json.getString("title");
|
String title = json.getString(JSON_KEY_TITLE);
|
||||||
if (title.isBlank()) {
|
if (title.isBlank()) {
|
||||||
return new AiResponseParsingFailure("BLANK_TITLE", "AI response field 'title' is blank");
|
return new AiResponseParsingFailure("BLANK_TITLE", "AI response field 'title' is blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate mandatory field: reasoning
|
// Validate mandatory field: reasoning
|
||||||
if (!json.has("reasoning") || json.isNull("reasoning")) {
|
if (!json.has(JSON_KEY_REASONING) || json.isNull(JSON_KEY_REASONING)) {
|
||||||
return new AiResponseParsingFailure("MISSING_REASONING", "AI response missing mandatory field 'reasoning'");
|
return new AiResponseParsingFailure("MISSING_REASONING", "AI response missing mandatory field 'reasoning'");
|
||||||
}
|
}
|
||||||
String reasoning = json.getString("reasoning");
|
String reasoning = json.getString(JSON_KEY_REASONING);
|
||||||
|
|
||||||
// Optional field: date
|
// Optional field: date
|
||||||
String dateString = null;
|
String dateString = null;
|
||||||
|
|||||||
+103
-36
@@ -70,6 +70,17 @@ public class DefaultManualFileCopyUseCase implements ManualFileCopyUseCase {
|
|||||||
private final ClockPort clock;
|
private final ClockPort clock;
|
||||||
private final ProcessingLogger logger;
|
private final ProcessingLogger logger;
|
||||||
|
|
||||||
|
/** Ergebnis der Dokument-Stammsatz-Auflösung: entweder ein Record oder ein Fehler. */
|
||||||
|
private record RecordLookupOutcome(DocumentRecord record, ManualFileCopyResult failure) {
|
||||||
|
boolean hasFailed() { return failure != null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ergebnis der Zieldateinamen-Auflösung: entweder Name + No-Op-Flag oder ein Fehler. */
|
||||||
|
private record FilenameLookupOutcome(String appliedFileName, boolean noOpIdentical,
|
||||||
|
ManualFileCopyResult failure) {
|
||||||
|
boolean hasFailed() { return failure != null; }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt den Use-Case mit allen erforderlichen Ports.
|
* Erstellt den Use-Case mit allen erforderlichen Ports.
|
||||||
*
|
*
|
||||||
@@ -127,72 +138,113 @@ public class DefaultManualFileCopyUseCase implements ManualFileCopyUseCase {
|
|||||||
logger.info("Manuelle Dateikopie angefordert: Fingerprint={}, Zielname={}",
|
logger.info("Manuelle Dateikopie angefordert: Fingerprint={}, Zielname={}",
|
||||||
fingerprint.sha256Hex(), desiredFullName);
|
fingerprint.sha256Hex(), desiredFullName);
|
||||||
|
|
||||||
// Schritt 1: Dokument-Stammsatz laden und Zustand prüfen
|
RecordLookupOutcome recordOutcome = loadAndValidateRecord(fingerprint);
|
||||||
|
if (recordOutcome.hasFailed()) {
|
||||||
|
return recordOutcome.failure();
|
||||||
|
}
|
||||||
|
DocumentRecord record = recordOutcome.record();
|
||||||
|
|
||||||
|
FilenameLookupOutcome filenameOutcome = resolveTargetFilename(fingerprint, desiredFullName);
|
||||||
|
if (filenameOutcome.hasFailed()) {
|
||||||
|
return filenameOutcome.failure();
|
||||||
|
}
|
||||||
|
String appliedFileName = filenameOutcome.appliedFileName();
|
||||||
|
boolean noOpIdentical = filenameOutcome.noOpIdentical();
|
||||||
|
|
||||||
|
if (!noOpIdentical) {
|
||||||
|
ManualFileCopyResult copyFailure = performFileCopy(fingerprint, record, appliedFileName);
|
||||||
|
if (copyFailure != null) {
|
||||||
|
return copyFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return persistAndBuildResult(fingerprint, record, appliedFileName, noOpIdentical, desiredFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt den Dokument-Stammsatz und prüft, ob der aktuelle Status eine manuelle
|
||||||
|
* Kopie erlaubt.
|
||||||
|
*
|
||||||
|
* @param fingerprint der Fingerprint des Dokuments
|
||||||
|
* @return Outcome mit geladenem Record oder mit einem Fehler-Ergebnis
|
||||||
|
*/
|
||||||
|
private RecordLookupOutcome loadAndValidateRecord(DocumentFingerprint fingerprint) {
|
||||||
DocumentRecordLookupResult lookupResult = repository.findByFingerprint(fingerprint);
|
DocumentRecordLookupResult lookupResult = repository.findByFingerprint(fingerprint);
|
||||||
|
|
||||||
DocumentRecord record;
|
|
||||||
if (lookupResult instanceof DocumentTerminalFinalFailure terminalFailure) {
|
if (lookupResult instanceof DocumentTerminalFinalFailure terminalFailure) {
|
||||||
record = terminalFailure.record();
|
return new RecordLookupOutcome(terminalFailure.record(), null);
|
||||||
} else if (lookupResult instanceof DocumentKnownProcessable known) {
|
} else if (lookupResult instanceof DocumentKnownProcessable known) {
|
||||||
record = known.record();
|
DocumentRecord record = known.record();
|
||||||
ProcessingStatus status = record.overallStatus();
|
if (record.overallStatus() == ProcessingStatus.SUCCESS) {
|
||||||
if (status == ProcessingStatus.SUCCESS) {
|
|
||||||
// Defensiv: SUCCESS sollte über DocumentTerminalSuccess auflösen, nicht hier.
|
|
||||||
logger.warn("Manuelle Dateikopie verweigert: Dokument bereits SUCCESS. Fingerprint={}",
|
logger.warn("Manuelle Dateikopie verweigert: Dokument bereits SUCCESS. Fingerprint={}",
|
||||||
fingerprint.sha256Hex());
|
fingerprint.sha256Hex());
|
||||||
return new ManualFileCopyInvalidState(
|
return new RecordLookupOutcome(null, new ManualFileCopyInvalidState(
|
||||||
"Dokument ist bereits erfolgreich verarbeitet. Bitte die Umbenennung der "
|
"Dokument ist bereits erfolgreich verarbeitet. Bitte die Umbenennung der "
|
||||||
+ "Zieldatei verwenden.");
|
+ "Zieldatei verwenden."));
|
||||||
}
|
}
|
||||||
|
return new RecordLookupOutcome(record, null);
|
||||||
} else if (lookupResult instanceof DocumentTerminalSuccess) {
|
} else if (lookupResult instanceof DocumentTerminalSuccess) {
|
||||||
logger.warn("Manuelle Dateikopie verweigert: Dokument bereits SUCCESS. Fingerprint={}",
|
logger.warn("Manuelle Dateikopie verweigert: Dokument bereits SUCCESS. Fingerprint={}",
|
||||||
fingerprint.sha256Hex());
|
fingerprint.sha256Hex());
|
||||||
return new ManualFileCopyInvalidState(
|
return new RecordLookupOutcome(null, new ManualFileCopyInvalidState(
|
||||||
"Dokument ist bereits erfolgreich verarbeitet. Bitte die Umbenennung der "
|
"Dokument ist bereits erfolgreich verarbeitet. Bitte die Umbenennung der "
|
||||||
+ "Zieldatei verwenden.");
|
+ "Zieldatei verwenden."));
|
||||||
} else if (lookupResult instanceof DocumentUnknown) {
|
} else if (lookupResult instanceof DocumentUnknown) {
|
||||||
logger.warn("Manuelle Dateikopie verweigert: Dokument unbekannt. Fingerprint={}",
|
logger.warn("Manuelle Dateikopie verweigert: Dokument unbekannt. Fingerprint={}",
|
||||||
fingerprint.sha256Hex());
|
fingerprint.sha256Hex());
|
||||||
return new ManualFileCopyDocumentNotFound(
|
return new RecordLookupOutcome(null, new ManualFileCopyDocumentNotFound(
|
||||||
"Kein Dokument mit dem angegebenen Fingerprint gefunden.");
|
"Kein Dokument mit dem angegebenen Fingerprint gefunden."));
|
||||||
} else if (lookupResult instanceof PersistenceLookupTechnicalFailure failure) {
|
} else if (lookupResult instanceof PersistenceLookupTechnicalFailure failure) {
|
||||||
logger.warn("Manuelle Dateikopie fehlgeschlagen: Lookup-Fehler. Fingerprint={}, Ursache={}",
|
logger.warn("Manuelle Dateikopie fehlgeschlagen: Lookup-Fehler. Fingerprint={}, Ursache={}",
|
||||||
fingerprint.sha256Hex(), failure.errorMessage());
|
fingerprint.sha256Hex(), failure.errorMessage());
|
||||||
return new ManualFileCopyPersistenceFailure(
|
return new RecordLookupOutcome(null, new ManualFileCopyPersistenceFailure(
|
||||||
"Persistenzfehler beim Laden des Dokumentstammsatzes: " + failure.errorMessage());
|
"Persistenzfehler beim Laden des Dokumentstammsatzes: " + failure.errorMessage()));
|
||||||
} else {
|
}
|
||||||
// Defensiv: nicht erreichbar dank sealed type, aber erforderlich für die Compiler-
|
// Defensiv: nicht erreichbar dank sealed type, aber erforderlich für die Compiler-
|
||||||
// Vollständigkeitsprüfung in älteren Werkzeugen.
|
// Vollständigkeitsprüfung in älteren Werkzeugen.
|
||||||
return new ManualFileCopyDocumentNotFound(
|
return new RecordLookupOutcome(null, new ManualFileCopyDocumentNotFound(
|
||||||
"Unbekanntes Lookup-Ergebnis: " + lookupResult.getClass().getSimpleName());
|
"Unbekanntes Lookup-Ergebnis: " + lookupResult.getClass().getSimpleName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schritt 2: Eindeutigen Zieldateinamen über TargetFolderPort auflösen
|
/**
|
||||||
|
* Löst über {@link TargetFolderPort} einen eindeutigen Zieldateinamen auf.
|
||||||
|
*
|
||||||
|
* @param fingerprint der Fingerprint des Dokuments
|
||||||
|
* @param desiredFullName der gewünschte vollständige Dateiname
|
||||||
|
* @return Outcome mit aufgelöstem Dateinamen und No-Op-Flag oder mit einem Fehler-Ergebnis
|
||||||
|
*/
|
||||||
|
private FilenameLookupOutcome resolveTargetFilename(DocumentFingerprint fingerprint,
|
||||||
|
String desiredFullName) {
|
||||||
TargetFilenameResolutionResult resolutionResult =
|
TargetFilenameResolutionResult resolutionResult =
|
||||||
targetFolderPort.resolveUniqueFilename(desiredFullName, fingerprint);
|
targetFolderPort.resolveUniqueFilename(desiredFullName, fingerprint);
|
||||||
|
|
||||||
boolean noOpIdentical = false;
|
|
||||||
String appliedFileName;
|
|
||||||
|
|
||||||
if (resolutionResult instanceof ExistingIdenticalTargetFile identical) {
|
if (resolutionResult instanceof ExistingIdenticalTargetFile identical) {
|
||||||
noOpIdentical = true;
|
|
||||||
appliedFileName = identical.existingFilename();
|
|
||||||
logger.info("Manuelle Dateikopie: Identische Datei bereits im Zielordner vorhanden. Fingerprint={}",
|
logger.info("Manuelle Dateikopie: Identische Datei bereits im Zielordner vorhanden. Fingerprint={}",
|
||||||
fingerprint.sha256Hex());
|
fingerprint.sha256Hex());
|
||||||
|
return new FilenameLookupOutcome(identical.existingFilename(), true, null);
|
||||||
} else if (resolutionResult instanceof TargetFolderTechnicalFailure folderFailure) {
|
} else if (resolutionResult instanceof TargetFolderTechnicalFailure folderFailure) {
|
||||||
logger.warn("Manuelle Dateikopie fehlgeschlagen: Zielordnerzugriff. Fingerprint={}, Ursache={}",
|
logger.warn("Manuelle Dateikopie fehlgeschlagen: Zielordnerzugriff. Fingerprint={}, Ursache={}",
|
||||||
fingerprint.sha256Hex(), folderFailure.errorMessage());
|
fingerprint.sha256Hex(), folderFailure.errorMessage());
|
||||||
return new ManualFileCopyFileSystemFailure(
|
return new FilenameLookupOutcome(null, false, new ManualFileCopyFileSystemFailure(
|
||||||
"Zielordner nicht zugänglich: " + folderFailure.errorMessage());
|
"Zielordner nicht zugänglich: " + folderFailure.errorMessage()));
|
||||||
} else if (resolutionResult instanceof ResolvedTargetFilename resolved) {
|
} else if (resolutionResult instanceof ResolvedTargetFilename resolved) {
|
||||||
appliedFileName = resolved.resolvedFilename();
|
return new FilenameLookupOutcome(resolved.resolvedFilename(), false, null);
|
||||||
} else {
|
}
|
||||||
return new ManualFileCopyFileSystemFailure(
|
return new FilenameLookupOutcome(null, false, new ManualFileCopyFileSystemFailure(
|
||||||
"Unbekanntes Auflösungsergebnis: " + resolutionResult.getClass().getSimpleName());
|
"Unbekanntes Auflösungsergebnis: " + resolutionResult.getClass().getSimpleName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schritt 3: Quelldatei kopieren – nur wenn keine identische Zieldatei existiert
|
/**
|
||||||
if (!noOpIdentical) {
|
* Kopiert die Quelldatei physisch in den Zielordner.
|
||||||
|
*
|
||||||
|
* @param fingerprint der Fingerprint des Dokuments (für Logging)
|
||||||
|
* @param record der aktuelle Dokument-Stammsatz
|
||||||
|
* @param appliedFileName der aufgelöste Zieldateiname
|
||||||
|
* @return ein Fehler-Ergebnis bei Misserfolg, {@code null} bei Erfolg
|
||||||
|
*/
|
||||||
|
private ManualFileCopyResult performFileCopy(DocumentFingerprint fingerprint,
|
||||||
|
DocumentRecord record,
|
||||||
|
String appliedFileName) {
|
||||||
var copyResult = targetFileCopyPort.copyToTarget(
|
var copyResult = targetFileCopyPort.copyToTarget(
|
||||||
record.lastKnownSourceLocator(), appliedFileName);
|
record.lastKnownSourceLocator(), appliedFileName);
|
||||||
if (copyResult instanceof TargetFileCopyTechnicalFailure technicalFailure) {
|
if (copyResult instanceof TargetFileCopyTechnicalFailure technicalFailure) {
|
||||||
@@ -204,9 +256,26 @@ public class DefaultManualFileCopyUseCase implements ManualFileCopyUseCase {
|
|||||||
return new ManualFileCopyFileSystemFailure(
|
return new ManualFileCopyFileSystemFailure(
|
||||||
"Unbekanntes Kopier-Ergebnis: " + copyResult.getClass().getSimpleName());
|
"Unbekanntes Kopier-Ergebnis: " + copyResult.getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schritt 4: Dokument-Stammsatz aktualisieren
|
/**
|
||||||
|
* Aktualisiert den Dokument-Stammsatz in der Persistenz und gibt das finale
|
||||||
|
* Operationsergebnis zurück. Bei einem Persistenzfehler nach erfolgter Zielkopie
|
||||||
|
* wird ein Best-Effort-Rollback der neu geschriebenen Datei durchgeführt.
|
||||||
|
*
|
||||||
|
* @param fingerprint der Fingerprint des Dokuments
|
||||||
|
* @param record der bisher gültige Dokument-Stammsatz
|
||||||
|
* @param appliedFileName der tatsächlich verwendete Zieldateiname
|
||||||
|
* @param noOpIdentical true, wenn keine neue Kopie geschrieben wurde
|
||||||
|
* @param desiredFullName der ursprünglich gewünschte Zieldateiname
|
||||||
|
* @return das finale Operationsergebnis
|
||||||
|
*/
|
||||||
|
private ManualFileCopyResult persistAndBuildResult(DocumentFingerprint fingerprint,
|
||||||
|
DocumentRecord record,
|
||||||
|
String appliedFileName,
|
||||||
|
boolean noOpIdentical,
|
||||||
|
String desiredFullName) {
|
||||||
var now = clock.now();
|
var now = clock.now();
|
||||||
DocumentRecord updatedRecord = new DocumentRecord(
|
DocumentRecord updatedRecord = new DocumentRecord(
|
||||||
record.fingerprint(),
|
record.fingerprint(),
|
||||||
@@ -248,17 +317,15 @@ public class DefaultManualFileCopyUseCase implements ManualFileCopyUseCase {
|
|||||||
"Persistenzfehler nach Kopie: " + errorMessage);
|
"Persistenzfehler nach Kopie: " + errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean conflictSuffixApplied = !noOpIdentical && !appliedFileName.equals(desiredFullName);
|
|
||||||
|
|
||||||
if (noOpIdentical) {
|
if (noOpIdentical) {
|
||||||
logger.info("Manuelle Dateikopie abgeschlossen ohne Schreibvorgang: identische Zieldatei {}.",
|
logger.info("Manuelle Dateikopie abgeschlossen ohne Schreibvorgang: identische Zieldatei {}.",
|
||||||
appliedFileName);
|
appliedFileName);
|
||||||
return new ManualFileCopyNoOpIdenticalTarget(appliedFileName);
|
return new ManualFileCopyNoOpIdenticalTarget(appliedFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean conflictSuffixApplied = !appliedFileName.equals(desiredFullName);
|
||||||
logger.info("Manuelle Dateikopie erfolgreich: {} (Suffix angewendet: {})",
|
logger.info("Manuelle Dateikopie erfolgreich: {} (Suffix angewendet: {})",
|
||||||
appliedFileName, conflictSuffixApplied);
|
appliedFileName, conflictSuffixApplied);
|
||||||
|
|
||||||
return new ManualFileCopySuccess(appliedFileName, conflictSuffixApplied);
|
return new ManualFileCopySuccess(appliedFileName, conflictSuffixApplied);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-4
@@ -24,6 +24,8 @@ public record EditorValidationFinding(
|
|||||||
Optional<String> fieldKey,
|
Optional<String> fieldKey,
|
||||||
EditorValidationSeverity severity,
|
EditorValidationSeverity severity,
|
||||||
String message) {
|
String message) {
|
||||||
|
private static final String FIELD_KEY_NOT_NULL = "fieldKey must not be null";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt einen neuen Validierungsbefund.
|
* Erstellt einen neuen Validierungsbefund.
|
||||||
@@ -47,7 +49,7 @@ public record EditorValidationFinding(
|
|||||||
* @return ein neuer Befund mit Schweregrad {@link EditorValidationSeverity#ERROR}
|
* @return ein neuer Befund mit Schweregrad {@link EditorValidationSeverity#ERROR}
|
||||||
*/
|
*/
|
||||||
public static EditorValidationFinding error(String fieldKey, String message) {
|
public static EditorValidationFinding error(String fieldKey, String message) {
|
||||||
Objects.requireNonNull(fieldKey, "fieldKey must not be null");
|
Objects.requireNonNull(fieldKey, FIELD_KEY_NOT_NULL);
|
||||||
return new EditorValidationFinding(Optional.of(fieldKey), EditorValidationSeverity.ERROR, message);
|
return new EditorValidationFinding(Optional.of(fieldKey), EditorValidationSeverity.ERROR, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +61,7 @@ public record EditorValidationFinding(
|
|||||||
* @return ein neuer Befund mit Schweregrad {@link EditorValidationSeverity#WARNING}
|
* @return ein neuer Befund mit Schweregrad {@link EditorValidationSeverity#WARNING}
|
||||||
*/
|
*/
|
||||||
public static EditorValidationFinding warning(String fieldKey, String message) {
|
public static EditorValidationFinding warning(String fieldKey, String message) {
|
||||||
Objects.requireNonNull(fieldKey, "fieldKey must not be null");
|
Objects.requireNonNull(fieldKey, FIELD_KEY_NOT_NULL);
|
||||||
return new EditorValidationFinding(Optional.of(fieldKey), EditorValidationSeverity.WARNING, message);
|
return new EditorValidationFinding(Optional.of(fieldKey), EditorValidationSeverity.WARNING, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ public record EditorValidationFinding(
|
|||||||
* @return ein neuer Befund mit Schweregrad {@link EditorValidationSeverity#HINT}
|
* @return ein neuer Befund mit Schweregrad {@link EditorValidationSeverity#HINT}
|
||||||
*/
|
*/
|
||||||
public static EditorValidationFinding hint(String fieldKey, String message) {
|
public static EditorValidationFinding hint(String fieldKey, String message) {
|
||||||
Objects.requireNonNull(fieldKey, "fieldKey must not be null");
|
Objects.requireNonNull(fieldKey, FIELD_KEY_NOT_NULL);
|
||||||
return new EditorValidationFinding(Optional.of(fieldKey), EditorValidationSeverity.HINT, message);
|
return new EditorValidationFinding(Optional.of(fieldKey), EditorValidationSeverity.HINT, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +95,7 @@ public record EditorValidationFinding(
|
|||||||
* @return ein neuer Befund mit Schweregrad {@link EditorValidationSeverity#INFO}
|
* @return ein neuer Befund mit Schweregrad {@link EditorValidationSeverity#INFO}
|
||||||
*/
|
*/
|
||||||
public static EditorValidationFinding info(String fieldKey, String message) {
|
public static EditorValidationFinding info(String fieldKey, String message) {
|
||||||
Objects.requireNonNull(fieldKey, "fieldKey must not be null");
|
Objects.requireNonNull(fieldKey, FIELD_KEY_NOT_NULL);
|
||||||
return new EditorValidationFinding(Optional.of(fieldKey), EditorValidationSeverity.INFO, message);
|
return new EditorValidationFinding(Optional.of(fieldKey), EditorValidationSeverity.INFO, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -21,6 +21,8 @@ public sealed interface CorrectionOutcome
|
|||||||
permits CorrectionOutcome.Applied,
|
permits CorrectionOutcome.Applied,
|
||||||
CorrectionOutcome.Failed,
|
CorrectionOutcome.Failed,
|
||||||
CorrectionOutcome.NotAttempted {
|
CorrectionOutcome.NotAttempted {
|
||||||
|
String SUGGESTION_NOT_NULL = "suggestion must not be null";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt den Korrekturvorschlag zurück, auf den sich dieses Ergebnis bezieht.
|
* Gibt den Korrekturvorschlag zurück, auf den sich dieses Ergebnis bezieht.
|
||||||
@@ -47,7 +49,7 @@ public sealed interface CorrectionOutcome
|
|||||||
* @throws NullPointerException wenn ein Parameter {@code null} ist
|
* @throws NullPointerException wenn ein Parameter {@code null} ist
|
||||||
*/
|
*/
|
||||||
public Applied {
|
public Applied {
|
||||||
Objects.requireNonNull(suggestion, "suggestion must not be null");
|
Objects.requireNonNull(suggestion, SUGGESTION_NOT_NULL);
|
||||||
Objects.requireNonNull(message, "message must not be null");
|
Objects.requireNonNull(message, "message must not be null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +72,7 @@ public sealed interface CorrectionOutcome
|
|||||||
* @throws NullPointerException wenn ein Parameter {@code null} ist
|
* @throws NullPointerException wenn ein Parameter {@code null} ist
|
||||||
*/
|
*/
|
||||||
public Failed {
|
public Failed {
|
||||||
Objects.requireNonNull(suggestion, "suggestion must not be null");
|
Objects.requireNonNull(suggestion, SUGGESTION_NOT_NULL);
|
||||||
Objects.requireNonNull(errorMessage, "errorMessage must not be null");
|
Objects.requireNonNull(errorMessage, "errorMessage must not be null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +99,7 @@ public sealed interface CorrectionOutcome
|
|||||||
* @throws NullPointerException wenn ein Parameter {@code null} ist
|
* @throws NullPointerException wenn ein Parameter {@code null} ist
|
||||||
*/
|
*/
|
||||||
public NotAttempted {
|
public NotAttempted {
|
||||||
Objects.requireNonNull(suggestion, "suggestion must not be null");
|
Objects.requireNonNull(suggestion, SUGGESTION_NOT_NULL);
|
||||||
Objects.requireNonNull(reason, "reason must not be null");
|
Objects.requireNonNull(reason, "reason must not be null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-6
@@ -21,6 +21,9 @@ public sealed interface CorrectionSuggestion
|
|||||||
permits CorrectionSuggestion.CreateDirectory,
|
permits CorrectionSuggestion.CreateDirectory,
|
||||||
CorrectionSuggestion.CreatePromptFile,
|
CorrectionSuggestion.CreatePromptFile,
|
||||||
CorrectionSuggestion.PrepareSqlitePath {
|
CorrectionSuggestion.PrepareSqlitePath {
|
||||||
|
String PATH_NOT_NULL = "path must not be null";
|
||||||
|
String DESCRIPTION_NOT_NULL = "descriptionForUser must not be null";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt eine kurze deutsche Beschreibung der vorgeschlagenen Korrektur zurück,
|
* Gibt eine kurze deutsche Beschreibung der vorgeschlagenen Korrektur zurück,
|
||||||
@@ -53,8 +56,8 @@ public sealed interface CorrectionSuggestion
|
|||||||
* @throws IllegalArgumentException wenn {@code path} leer ist
|
* @throws IllegalArgumentException wenn {@code path} leer ist
|
||||||
*/
|
*/
|
||||||
public CreateDirectory {
|
public CreateDirectory {
|
||||||
Objects.requireNonNull(path, "path must not be null");
|
Objects.requireNonNull(path, PATH_NOT_NULL);
|
||||||
Objects.requireNonNull(descriptionForUser, "descriptionForUser must not be null");
|
Objects.requireNonNull(descriptionForUser, DESCRIPTION_NOT_NULL);
|
||||||
if (path.isBlank()) {
|
if (path.isBlank()) {
|
||||||
throw new IllegalArgumentException("path must not be blank");
|
throw new IllegalArgumentException("path must not be blank");
|
||||||
}
|
}
|
||||||
@@ -93,8 +96,8 @@ public sealed interface CorrectionSuggestion
|
|||||||
* {@code maxTitleLength < 1}
|
* {@code maxTitleLength < 1}
|
||||||
*/
|
*/
|
||||||
public CreatePromptFile {
|
public CreatePromptFile {
|
||||||
Objects.requireNonNull(path, "path must not be null");
|
Objects.requireNonNull(path, PATH_NOT_NULL);
|
||||||
Objects.requireNonNull(descriptionForUser, "descriptionForUser must not be null");
|
Objects.requireNonNull(descriptionForUser, DESCRIPTION_NOT_NULL);
|
||||||
if (path.isBlank()) {
|
if (path.isBlank()) {
|
||||||
throw new IllegalArgumentException("path must not be blank");
|
throw new IllegalArgumentException("path must not be blank");
|
||||||
}
|
}
|
||||||
@@ -129,8 +132,8 @@ public sealed interface CorrectionSuggestion
|
|||||||
* @throws IllegalArgumentException wenn {@code path} leer ist
|
* @throws IllegalArgumentException wenn {@code path} leer ist
|
||||||
*/
|
*/
|
||||||
public PrepareSqlitePath {
|
public PrepareSqlitePath {
|
||||||
Objects.requireNonNull(path, "path must not be null");
|
Objects.requireNonNull(path, PATH_NOT_NULL);
|
||||||
Objects.requireNonNull(descriptionForUser, "descriptionForUser must not be null");
|
Objects.requireNonNull(descriptionForUser, DESCRIPTION_NOT_NULL);
|
||||||
if (path.isBlank()) {
|
if (path.isBlank()) {
|
||||||
throw new IllegalArgumentException("path must not be blank");
|
throw new IllegalArgumentException("path must not be blank");
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -1652,6 +1652,7 @@ class BatchRunProcessingUseCaseTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void debugSensitiveAiContent(String message, Object... args) {
|
public void debugSensitiveAiContent(String message, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+39
-13
@@ -300,8 +300,12 @@ class BatchRunProgressObservationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final class NoOpLock implements RunLockPort {
|
private static final class NoOpLock implements RunLockPort {
|
||||||
@Override public void acquire() { }
|
@Override public void acquire() {
|
||||||
@Override public void release() { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void release() {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public java.util.Optional<de.gecheckt.pdf.umbenenner.application.port.out.RunLockHandle> tryAcquire() {
|
public java.util.Optional<de.gecheckt.pdf.umbenenner.application.port.out.RunLockHandle> tryAcquire() {
|
||||||
return java.util.Optional.empty();
|
return java.util.Optional.empty();
|
||||||
@@ -335,11 +339,21 @@ class BatchRunProgressObservationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final class SilentLogger implements ProcessingLogger {
|
private static final class SilentLogger implements ProcessingLogger {
|
||||||
@Override public void info(String message, Object... args) { }
|
@Override public void info(String message, Object... args) {
|
||||||
@Override public void warn(String message, Object... args) { }
|
// intentionally empty
|
||||||
@Override public void error(String message, Object... args) { }
|
}
|
||||||
@Override public void debug(String message, Object... args) { }
|
@Override public void warn(String message, Object... args) {
|
||||||
@Override public void debugSensitiveAiContent(String message, Object... args) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void error(String message, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void debug(String message, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void debugSensitiveAiContent(String message, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class RecordingObserver implements BatchRunProgressObserver {
|
private static final class RecordingObserver implements BatchRunProgressObserver {
|
||||||
@@ -465,21 +479,31 @@ class BatchRunProgressObservationTest {
|
|||||||
@Override public DocumentRecordLookupResult findByFingerprint(DocumentFingerprint f) {
|
@Override public DocumentRecordLookupResult findByFingerprint(DocumentFingerprint f) {
|
||||||
return new DocumentUnknown();
|
return new DocumentUnknown();
|
||||||
}
|
}
|
||||||
@Override public void create(de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord record) { }
|
@Override public void create(de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord record) {
|
||||||
@Override public void update(de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord record) { }
|
// intentionally empty
|
||||||
@Override public void deleteByFingerprint(DocumentFingerprint fingerprint) { }
|
}
|
||||||
|
@Override public void update(de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void deleteByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class NoAttempts implements ProcessingAttemptRepository {
|
private static final class NoAttempts implements ProcessingAttemptRepository {
|
||||||
static final NoAttempts INSTANCE = new NoAttempts();
|
static final NoAttempts INSTANCE = new NoAttempts();
|
||||||
@Override public int loadNextAttemptNumber(DocumentFingerprint fingerprint) { return 1; }
|
@Override public int loadNextAttemptNumber(DocumentFingerprint fingerprint) { return 1; }
|
||||||
@Override public void save(ProcessingAttempt attempt) { }
|
@Override public void save(ProcessingAttempt attempt) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override public List<ProcessingAttempt> findAllByFingerprint(DocumentFingerprint fingerprint) {
|
@Override public List<ProcessingAttempt> findAllByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
@Override public ProcessingAttempt findLatestProposalReadyAttempt(
|
@Override public ProcessingAttempt findLatestProposalReadyAttempt(
|
||||||
DocumentFingerprint fingerprint) { return null; }
|
DocumentFingerprint fingerprint) { return null; }
|
||||||
@Override public void deleteAllByFingerprint(DocumentFingerprint fingerprint) { }
|
@Override public void deleteAllByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class NoUow implements UnitOfWorkPort {
|
private static final class NoUow implements UnitOfWorkPort {
|
||||||
@@ -499,7 +523,9 @@ class BatchRunProgressObservationTest {
|
|||||||
return new ResolvedTargetFilename(baseFilename);
|
return new ResolvedTargetFilename(baseFilename);
|
||||||
}
|
}
|
||||||
@Override public String getTargetFolderLocator() { return "/tmp/target"; }
|
@Override public String getTargetFolderLocator() { return "/tmp/target"; }
|
||||||
@Override public void tryDeleteTargetFile(String filename) { }
|
@Override public void tryDeleteTargetFile(String filename) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class NoTargetCopy implements TargetFileCopyPort {
|
private static final class NoTargetCopy implements TargetFileCopyPort {
|
||||||
|
|||||||
+36
-12
@@ -83,17 +83,25 @@ class DefaultDeleteDocumentHistoryUseCaseTest {
|
|||||||
UnitOfWorkPort failingPort = operations ->
|
UnitOfWorkPort failingPort = operations ->
|
||||||
operations.accept(new UnitOfWorkPort.TransactionOperations() {
|
operations.accept(new UnitOfWorkPort.TransactionOperations() {
|
||||||
@Override
|
@Override
|
||||||
public void saveProcessingAttempt(ProcessingAttempt attempt) { }
|
public void saveProcessingAttempt(ProcessingAttempt attempt) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void createDocumentRecord(DocumentRecord record) { }
|
public void createDocumentRecord(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void updateDocumentRecord(DocumentRecord record) { }
|
public void updateDocumentRecord(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
|
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
throw new DocumentPersistenceException("Simulated DB error");
|
throw new DocumentPersistenceException("Simulated DB error");
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) { }
|
public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
DefaultDeleteDocumentHistoryUseCase useCase =
|
DefaultDeleteDocumentHistoryUseCase useCase =
|
||||||
@@ -110,11 +118,21 @@ class DefaultDeleteDocumentHistoryUseCaseTest {
|
|||||||
|
|
||||||
private static UnitOfWorkPort noOpPort() {
|
private static UnitOfWorkPort noOpPort() {
|
||||||
return operations -> operations.accept(new UnitOfWorkPort.TransactionOperations() {
|
return operations -> operations.accept(new UnitOfWorkPort.TransactionOperations() {
|
||||||
@Override public void saveProcessingAttempt(ProcessingAttempt a) { }
|
@Override public void saveProcessingAttempt(ProcessingAttempt a) {
|
||||||
@Override public void createDocumentRecord(DocumentRecord r) { }
|
// intentionally empty
|
||||||
@Override public void updateDocumentRecord(DocumentRecord r) { }
|
}
|
||||||
@Override public void resetDocumentByFingerprint(DocumentFingerprint fp) { }
|
@Override public void createDocumentRecord(DocumentRecord r) {
|
||||||
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fp) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void updateDocumentRecord(DocumentRecord r) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void resetDocumentByFingerprint(DocumentFingerprint fp) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fp) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,9 +145,15 @@ class DefaultDeleteDocumentHistoryUseCaseTest {
|
|||||||
final List<DocumentFingerprint> resetByFingerprintFingerprints = new ArrayList<>();
|
final List<DocumentFingerprint> resetByFingerprintFingerprints = new ArrayList<>();
|
||||||
final List<DocumentFingerprint> resetStatusForRetryFingerprints = new ArrayList<>();
|
final List<DocumentFingerprint> resetStatusForRetryFingerprints = new ArrayList<>();
|
||||||
|
|
||||||
@Override public void saveProcessingAttempt(ProcessingAttempt a) { }
|
@Override public void saveProcessingAttempt(ProcessingAttempt a) {
|
||||||
@Override public void createDocumentRecord(DocumentRecord r) { }
|
// intentionally empty
|
||||||
@Override public void updateDocumentRecord(DocumentRecord r) { }
|
}
|
||||||
|
@Override public void createDocumentRecord(DocumentRecord r) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void updateDocumentRecord(DocumentRecord r) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
|
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
|||||||
+36
-12
@@ -84,13 +84,21 @@ class DefaultHistoryResetDocumentStatusUseCaseTest {
|
|||||||
UnitOfWorkPort failingPort = operations ->
|
UnitOfWorkPort failingPort = operations ->
|
||||||
operations.accept(new UnitOfWorkPort.TransactionOperations() {
|
operations.accept(new UnitOfWorkPort.TransactionOperations() {
|
||||||
@Override
|
@Override
|
||||||
public void saveProcessingAttempt(ProcessingAttempt attempt) { }
|
public void saveProcessingAttempt(ProcessingAttempt attempt) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void createDocumentRecord(DocumentRecord record) { }
|
public void createDocumentRecord(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void updateDocumentRecord(DocumentRecord record) { }
|
public void updateDocumentRecord(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) { }
|
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
|
public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
|
||||||
throw new DocumentPersistenceException("Simulated DB error");
|
throw new DocumentPersistenceException("Simulated DB error");
|
||||||
@@ -111,11 +119,21 @@ class DefaultHistoryResetDocumentStatusUseCaseTest {
|
|||||||
|
|
||||||
private static UnitOfWorkPort noOpPort() {
|
private static UnitOfWorkPort noOpPort() {
|
||||||
return operations -> operations.accept(new UnitOfWorkPort.TransactionOperations() {
|
return operations -> operations.accept(new UnitOfWorkPort.TransactionOperations() {
|
||||||
@Override public void saveProcessingAttempt(ProcessingAttempt a) { }
|
@Override public void saveProcessingAttempt(ProcessingAttempt a) {
|
||||||
@Override public void createDocumentRecord(DocumentRecord r) { }
|
// intentionally empty
|
||||||
@Override public void updateDocumentRecord(DocumentRecord r) { }
|
}
|
||||||
@Override public void resetDocumentByFingerprint(DocumentFingerprint fp) { }
|
@Override public void createDocumentRecord(DocumentRecord r) {
|
||||||
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fp) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void updateDocumentRecord(DocumentRecord r) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void resetDocumentByFingerprint(DocumentFingerprint fp) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fp) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,9 +146,15 @@ class DefaultHistoryResetDocumentStatusUseCaseTest {
|
|||||||
final List<DocumentFingerprint> resetStatusForRetryFingerprints = new ArrayList<>();
|
final List<DocumentFingerprint> resetStatusForRetryFingerprints = new ArrayList<>();
|
||||||
final List<DocumentFingerprint> resetByFingerprintFingerprints = new ArrayList<>();
|
final List<DocumentFingerprint> resetByFingerprintFingerprints = new ArrayList<>();
|
||||||
|
|
||||||
@Override public void saveProcessingAttempt(ProcessingAttempt a) { }
|
@Override public void saveProcessingAttempt(ProcessingAttempt a) {
|
||||||
@Override public void createDocumentRecord(DocumentRecord r) { }
|
// intentionally empty
|
||||||
@Override public void updateDocumentRecord(DocumentRecord r) { }
|
}
|
||||||
|
@Override public void createDocumentRecord(DocumentRecord r) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void updateDocumentRecord(DocumentRecord r) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
|
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
|||||||
+57
-19
@@ -100,11 +100,21 @@ class DefaultManualFileCopyUseCaseTest {
|
|||||||
|
|
||||||
private static ProcessingLogger noOpLogger() {
|
private static ProcessingLogger noOpLogger() {
|
||||||
return new ProcessingLogger() {
|
return new ProcessingLogger() {
|
||||||
@Override public void info(String msg, Object... args) { }
|
@Override public void info(String msg, Object... args) {
|
||||||
@Override public void debug(String msg, Object... args) { }
|
// intentionally empty
|
||||||
@Override public void debugSensitiveAiContent(String msg, Object... args) { }
|
}
|
||||||
@Override public void warn(String msg, Object... args) { }
|
@Override public void debug(String msg, Object... args) {
|
||||||
@Override public void error(String msg, Object... args) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void debugSensitiveAiContent(String msg, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void warn(String msg, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void error(String msg, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,9 +125,15 @@ class DefaultManualFileCopyUseCaseTest {
|
|||||||
private static DocumentRecordRepository repositoryReturning(DocumentRecordLookupResult result) {
|
private static DocumentRecordRepository repositoryReturning(DocumentRecordLookupResult result) {
|
||||||
return new DocumentRecordRepository() {
|
return new DocumentRecordRepository() {
|
||||||
@Override public DocumentRecordLookupResult findByFingerprint(DocumentFingerprint fp) { return result; }
|
@Override public DocumentRecordLookupResult findByFingerprint(DocumentFingerprint fp) { return result; }
|
||||||
@Override public void create(DocumentRecord r) { }
|
@Override public void create(DocumentRecord r) {
|
||||||
@Override public void update(DocumentRecord r) { }
|
// intentionally empty
|
||||||
@Override public void deleteByFingerprint(DocumentFingerprint fp) { }
|
}
|
||||||
|
@Override public void update(DocumentRecord r) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void deleteByFingerprint(DocumentFingerprint fp) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +141,9 @@ class DefaultManualFileCopyUseCaseTest {
|
|||||||
return new TargetFolderPort() {
|
return new TargetFolderPort() {
|
||||||
@Override public String getTargetFolderLocator() { return "/zielordner"; }
|
@Override public String getTargetFolderLocator() { return "/zielordner"; }
|
||||||
@Override public TargetFilenameResolutionResult resolveUniqueFilename(String baseName, DocumentFingerprint fp) { return result; }
|
@Override public TargetFilenameResolutionResult resolveUniqueFilename(String baseName, DocumentFingerprint fp) { return result; }
|
||||||
@Override public void tryDeleteTargetFile(String name) { }
|
@Override public void tryDeleteTargetFile(String name) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,7 +457,9 @@ class DefaultManualFileCopyUseCaseTest {
|
|||||||
baseNames.add(baseName);
|
baseNames.add(baseName);
|
||||||
return new ResolvedTargetFilename(baseName);
|
return new ResolvedTargetFilename(baseName);
|
||||||
}
|
}
|
||||||
@Override public void tryDeleteTargetFile(String name) { }
|
@Override public void tryDeleteTargetFile(String name) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
DefaultManualFileCopyUseCase useCase = new DefaultManualFileCopyUseCase(
|
DefaultManualFileCopyUseCase useCase = new DefaultManualFileCopyUseCase(
|
||||||
@@ -545,11 +565,21 @@ class DefaultManualFileCopyUseCaseTest {
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
private static class NoOpTransactionOperations implements UnitOfWorkPort.TransactionOperations {
|
private static class NoOpTransactionOperations implements UnitOfWorkPort.TransactionOperations {
|
||||||
@Override public void saveProcessingAttempt(ProcessingAttempt attempt) { }
|
@Override public void saveProcessingAttempt(ProcessingAttempt attempt) {
|
||||||
@Override public void createDocumentRecord(DocumentRecord record) { }
|
// intentionally empty
|
||||||
@Override public void updateDocumentRecord(DocumentRecord record) { }
|
}
|
||||||
@Override public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) { }
|
@Override public void createDocumentRecord(DocumentRecord record) {
|
||||||
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void updateDocumentRecord(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class RecordCapturingTransactionOperations implements UnitOfWorkPort.TransactionOperations {
|
private static class RecordCapturingTransactionOperations implements UnitOfWorkPort.TransactionOperations {
|
||||||
@@ -559,10 +589,18 @@ class DefaultManualFileCopyUseCaseTest {
|
|||||||
this.captured = captured;
|
this.captured = captured;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void saveProcessingAttempt(ProcessingAttempt attempt) { }
|
@Override public void saveProcessingAttempt(ProcessingAttempt attempt) {
|
||||||
@Override public void createDocumentRecord(DocumentRecord record) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void createDocumentRecord(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override public void updateDocumentRecord(DocumentRecord record) { captured.add(record); }
|
@Override public void updateDocumentRecord(DocumentRecord record) { captured.add(record); }
|
||||||
@Override public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) { }
|
@Override public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-19
@@ -118,11 +118,21 @@ class DefaultManualFileRenameUseCaseTest {
|
|||||||
|
|
||||||
private static ProcessingLogger noOpLogger() {
|
private static ProcessingLogger noOpLogger() {
|
||||||
return new ProcessingLogger() {
|
return new ProcessingLogger() {
|
||||||
@Override public void info(String msg, Object... args) { }
|
@Override public void info(String msg, Object... args) {
|
||||||
@Override public void debug(String msg, Object... args) { }
|
// intentionally empty
|
||||||
@Override public void debugSensitiveAiContent(String msg, Object... args) { }
|
}
|
||||||
@Override public void warn(String msg, Object... args) { }
|
@Override public void debug(String msg, Object... args) {
|
||||||
@Override public void error(String msg, Object... args) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void debugSensitiveAiContent(String msg, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void warn(String msg, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void error(String msg, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,9 +143,15 @@ class DefaultManualFileRenameUseCaseTest {
|
|||||||
private static DocumentRecordRepository repositoryReturning(DocumentRecordLookupResult result) {
|
private static DocumentRecordRepository repositoryReturning(DocumentRecordLookupResult result) {
|
||||||
return new DocumentRecordRepository() {
|
return new DocumentRecordRepository() {
|
||||||
@Override public DocumentRecordLookupResult findByFingerprint(DocumentFingerprint fp) { return result; }
|
@Override public DocumentRecordLookupResult findByFingerprint(DocumentFingerprint fp) { return result; }
|
||||||
@Override public void create(DocumentRecord r) { }
|
@Override public void create(DocumentRecord r) {
|
||||||
@Override public void update(DocumentRecord r) { }
|
// intentionally empty
|
||||||
@Override public void deleteByFingerprint(DocumentFingerprint fp) { }
|
}
|
||||||
|
@Override public void update(DocumentRecord r) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void deleteByFingerprint(DocumentFingerprint fp) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +159,9 @@ class DefaultManualFileRenameUseCaseTest {
|
|||||||
return new TargetFolderPort() {
|
return new TargetFolderPort() {
|
||||||
@Override public String getTargetFolderLocator() { return "/zielordner"; }
|
@Override public String getTargetFolderLocator() { return "/zielordner"; }
|
||||||
@Override public TargetFilenameResolutionResult resolveUniqueFilename(String baseName, DocumentFingerprint fp) { return result; }
|
@Override public TargetFilenameResolutionResult resolveUniqueFilename(String baseName, DocumentFingerprint fp) { return result; }
|
||||||
@Override public void tryDeleteTargetFile(String name) { }
|
@Override public void tryDeleteTargetFile(String name) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,7 +493,9 @@ class DefaultManualFileRenameUseCaseTest {
|
|||||||
folderArgs.add(new String[]{baseName});
|
folderArgs.add(new String[]{baseName});
|
||||||
return new ResolvedTargetFilename(baseName);
|
return new ResolvedTargetFilename(baseName);
|
||||||
}
|
}
|
||||||
@Override public void tryDeleteTargetFile(String name) { }
|
@Override public void tryDeleteTargetFile(String name) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
DefaultManualFileRenameUseCase useCase = new DefaultManualFileRenameUseCase(
|
DefaultManualFileRenameUseCase useCase = new DefaultManualFileRenameUseCase(
|
||||||
@@ -616,11 +636,21 @@ class DefaultManualFileRenameUseCaseTest {
|
|||||||
|
|
||||||
/** Führt keine Persistenzoperationen durch. */
|
/** Führt keine Persistenzoperationen durch. */
|
||||||
private static class NoOpTransactionOperations implements UnitOfWorkPort.TransactionOperations {
|
private static class NoOpTransactionOperations implements UnitOfWorkPort.TransactionOperations {
|
||||||
@Override public void saveProcessingAttempt(ProcessingAttempt attempt) { }
|
@Override public void saveProcessingAttempt(ProcessingAttempt attempt) {
|
||||||
@Override public void createDocumentRecord(DocumentRecord record) { }
|
// intentionally empty
|
||||||
@Override public void updateDocumentRecord(DocumentRecord record) { }
|
}
|
||||||
@Override public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) { }
|
@Override public void createDocumentRecord(DocumentRecord record) {
|
||||||
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void updateDocumentRecord(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Zeichnet updateDocumentRecord-Aufrufe auf. */
|
/** Zeichnet updateDocumentRecord-Aufrufe auf. */
|
||||||
@@ -631,10 +661,18 @@ class DefaultManualFileRenameUseCaseTest {
|
|||||||
this.captured = captured;
|
this.captured = captured;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void saveProcessingAttempt(ProcessingAttempt attempt) { }
|
@Override public void saveProcessingAttempt(ProcessingAttempt attempt) {
|
||||||
@Override public void createDocumentRecord(DocumentRecord record) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void createDocumentRecord(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override public void updateDocumentRecord(DocumentRecord record) { captured.add(record); }
|
@Override public void updateDocumentRecord(DocumentRecord record) { captured.add(record); }
|
||||||
@Override public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) { }
|
@Override public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-8
@@ -179,11 +179,21 @@ class DefaultResetDocumentStatusUseCaseTest {
|
|||||||
|
|
||||||
private static ProcessingLogger noOpLogger() {
|
private static ProcessingLogger noOpLogger() {
|
||||||
return new ProcessingLogger() {
|
return new ProcessingLogger() {
|
||||||
@Override public void info(String msg, Object... args) { }
|
@Override public void info(String msg, Object... args) {
|
||||||
@Override public void debug(String msg, Object... args) { }
|
// intentionally empty
|
||||||
@Override public void debugSensitiveAiContent(String msg, Object... args) { }
|
}
|
||||||
@Override public void warn(String msg, Object... args) { }
|
@Override public void debug(String msg, Object... args) {
|
||||||
@Override public void error(String msg, Object... args) { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void debugSensitiveAiContent(String msg, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void warn(String msg, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void error(String msg, Object... args) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,15 +212,21 @@ class DefaultResetDocumentStatusUseCaseTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveProcessingAttempt(
|
public void saveProcessingAttempt(
|
||||||
de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt attempt) { }
|
de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt attempt) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createDocumentRecord(
|
public void createDocumentRecord(
|
||||||
de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord record) { }
|
de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateDocumentRecord(
|
public void updateDocumentRecord(
|
||||||
de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord record) { }
|
de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
|
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
|||||||
+18
-6
@@ -128,11 +128,17 @@ class DefaultResolveHistoricalDocumentContextUseCaseTest {
|
|||||||
throw new DocumentPersistenceException("Verbindungsfehler", null);
|
throw new DocumentPersistenceException("Verbindungsfehler", null);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void create(DocumentRecord record) {}
|
public void create(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void update(DocumentRecord record) {}
|
public void update(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void deleteByFingerprint(DocumentFingerprint fingerprint) {}
|
public void deleteByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var useCase = new DefaultResolveHistoricalDocumentContextUseCase(throwingRepo);
|
var useCase = new DefaultResolveHistoricalDocumentContextUseCase(throwingRepo);
|
||||||
@@ -151,11 +157,17 @@ class DefaultResolveHistoricalDocumentContextUseCaseTest {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void create(DocumentRecord record) {}
|
public void create(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void update(DocumentRecord record) {}
|
public void update(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void deleteByFingerprint(DocumentFingerprint fingerprint) {}
|
public void deleteByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-6
@@ -104,11 +104,17 @@ class DefaultResolveHistoricalFileNameUseCaseTest {
|
|||||||
throw new DocumentPersistenceException("Verbindungsfehler", null);
|
throw new DocumentPersistenceException("Verbindungsfehler", null);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void create(DocumentRecord record) {}
|
public void create(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void update(DocumentRecord record) {}
|
public void update(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void deleteByFingerprint(DocumentFingerprint fingerprint) {}
|
public void deleteByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var useCase = new DefaultResolveHistoricalFileNameUseCase(throwingRepo);
|
var useCase = new DefaultResolveHistoricalFileNameUseCase(throwingRepo);
|
||||||
@@ -127,11 +133,17 @@ class DefaultResolveHistoricalFileNameUseCaseTest {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void create(DocumentRecord record) {}
|
public void create(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void update(DocumentRecord record) {}
|
public void update(DocumentRecord record) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void deleteByFingerprint(DocumentFingerprint fingerprint) {}
|
public void deleteByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+41
-33
@@ -209,6 +209,14 @@ import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
|||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class BootstrapRunner {
|
public class BootstrapRunner {
|
||||||
|
private static final String CONFIG_FILE_NOT_NULL = "configFilePath must not be null";
|
||||||
|
private static final String FINGERPRINT_NOT_NULL = "fingerprint must not be null";
|
||||||
|
private static final String UNEXPECTED_ERROR_PREFIX = "Unerwarteter Fehler: ";
|
||||||
|
private static final String CONFIG_LOAD_FAILED_PREFIX = "Konfiguration konnte nicht geladen werden: ";
|
||||||
|
private static final String CONFIG_FILE_NOT_FOUND_PREFIX = "Konfigurationsdatei nicht gefunden: ";
|
||||||
|
private static final String CONFIG_NOT_RUNNABLE_PREFIX = "Die Konfiguration ist nicht lauffähig: ";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(BootstrapRunner.class);
|
private static final Logger LOG = LogManager.getLogger(BootstrapRunner.class);
|
||||||
private static final Path DEFAULT_CONFIG_PATH = Paths.get("config/application.properties");
|
private static final Path DEFAULT_CONFIG_PATH = Paths.get("config/application.properties");
|
||||||
@@ -942,7 +950,7 @@ public class BootstrapRunner {
|
|||||||
configPath.toAbsolutePath());
|
configPath.toAbsolutePath());
|
||||||
return new GuiStartupContext(
|
return new GuiStartupContext(
|
||||||
GuiConfigurationEditorStateFactory.createBlankStartState(),
|
GuiConfigurationEditorStateFactory.createBlankStartState(),
|
||||||
Optional.of("Konfigurationsdatei nicht gefunden: " + configPath.toAbsolutePath()
|
Optional.of(CONFIG_FILE_NOT_FOUND_PREFIX + configPath.toAbsolutePath()
|
||||||
+ "\nDie GUI startet ohne Konfigurationsdatei."),
|
+ "\nDie GUI startet ohne Konfigurationsdatei."),
|
||||||
loader,
|
loader,
|
||||||
writer,
|
writer,
|
||||||
@@ -995,7 +1003,7 @@ public class BootstrapRunner {
|
|||||||
e.getMessage(), e);
|
e.getMessage(), e);
|
||||||
return new GuiStartupContext(
|
return new GuiStartupContext(
|
||||||
GuiConfigurationEditorStateFactory.createBlankStartState(),
|
GuiConfigurationEditorStateFactory.createBlankStartState(),
|
||||||
Optional.of("Konfiguration konnte nicht geladen werden: " + e.getMessage()),
|
Optional.of(CONFIG_LOAD_FAILED_PREFIX + e.getMessage()),
|
||||||
loader,
|
loader,
|
||||||
writer,
|
writer,
|
||||||
modelCatalogPort,
|
modelCatalogPort,
|
||||||
@@ -1074,7 +1082,7 @@ public class BootstrapRunner {
|
|||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
LOG.error("Scheduler-Tick: Unerwarteter Fehler: {}", e.getMessage(), e);
|
LOG.error("Scheduler-Tick: Unerwarteter Fehler: {}", e.getMessage(), e);
|
||||||
return new BatchRunTriggerResult.Failed(
|
return new BatchRunTriggerResult.Failed(
|
||||||
"Unerwarteter Fehler: " + e.getMessage(),
|
UNEXPECTED_ERROR_PREFIX + e.getMessage(),
|
||||||
e.getClass().getSimpleName());
|
e.getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1199,7 +1207,7 @@ public class BootstrapRunner {
|
|||||||
LOG.warn("GUI-Anwendungskontext: Konfiguration konnte nicht geladen werden: {}",
|
LOG.warn("GUI-Anwendungskontext: Konfiguration konnte nicht geladen werden: {}",
|
||||||
e.getMessage());
|
e.getMessage());
|
||||||
guiApplicationRunContext = Optional.empty();
|
guiApplicationRunContext = Optional.empty();
|
||||||
return Optional.of("Konfiguration konnte nicht geladen werden: " + e.getMessage());
|
return Optional.of(CONFIG_LOAD_FAILED_PREFIX + e.getMessage());
|
||||||
} catch (InvalidStartConfigurationException e) {
|
} catch (InvalidStartConfigurationException e) {
|
||||||
LOG.warn("GUI-Anwendungskontext: Konfiguration nicht lauffähig: {}", e.getMessage());
|
LOG.warn("GUI-Anwendungskontext: Konfiguration nicht lauffähig: {}", e.getMessage());
|
||||||
guiApplicationRunContext = Optional.empty();
|
guiApplicationRunContext = Optional.empty();
|
||||||
@@ -1329,7 +1337,7 @@ public class BootstrapRunner {
|
|||||||
}
|
}
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
LOG.error("GUI-Status-Reset: Unerwarteter Fehler: {}", e.getMessage(), e);
|
LOG.error("GUI-Status-Reset: Unerwarteter Fehler: {}", e.getMessage(), e);
|
||||||
return allFailures(fingerprints, "Unerwarteter Fehler: "
|
return allFailures(fingerprints, UNEXPECTED_ERROR_PREFIX
|
||||||
+ (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()));
|
+ (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1360,7 +1368,7 @@ public class BootstrapRunner {
|
|||||||
Path configFilePath,
|
Path configFilePath,
|
||||||
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver progressObserver,
|
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver progressObserver,
|
||||||
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunCancellationToken cancellationToken) {
|
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunCancellationToken cancellationToken) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
Objects.requireNonNull(progressObserver, "progressObserver must not be null");
|
Objects.requireNonNull(progressObserver, "progressObserver must not be null");
|
||||||
Objects.requireNonNull(cancellationToken, "cancellationToken must not be null");
|
Objects.requireNonNull(cancellationToken, "cancellationToken must not be null");
|
||||||
LOG.info("GUI-Verarbeitungslauf: Startanforderung für Konfiguration {}.", configFilePath);
|
LOG.info("GUI-Verarbeitungslauf: Startanforderung für Konfiguration {}.", configFilePath);
|
||||||
@@ -1390,7 +1398,7 @@ public class BootstrapRunner {
|
|||||||
LOG.error("GUI-Verarbeitungslauf: Konfiguration konnte nicht geladen werden: {}",
|
LOG.error("GUI-Verarbeitungslauf: Konfiguration konnte nicht geladen werden: {}",
|
||||||
e.getMessage(), e);
|
e.getMessage(), e);
|
||||||
return GuiBatchRunLaunchOutcome.rejected(
|
return GuiBatchRunLaunchOutcome.rejected(
|
||||||
"Konfiguration konnte nicht geladen werden: " + e.getMessage());
|
CONFIG_LOAD_FAILED_PREFIX + e.getMessage());
|
||||||
} catch (InvalidStartConfigurationException e) {
|
} catch (InvalidStartConfigurationException e) {
|
||||||
LOG.error("GUI-Verarbeitungslauf: Konfiguration ist nicht lauffähig: {}", e.getMessage());
|
LOG.error("GUI-Verarbeitungslauf: Konfiguration ist nicht lauffähig: {}", e.getMessage());
|
||||||
return GuiBatchRunLaunchOutcome.rejected(
|
return GuiBatchRunLaunchOutcome.rejected(
|
||||||
@@ -1448,7 +1456,7 @@ public class BootstrapRunner {
|
|||||||
Set<DocumentFingerprint> fingerprintFilter,
|
Set<DocumentFingerprint> fingerprintFilter,
|
||||||
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver progressObserver,
|
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver progressObserver,
|
||||||
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunCancellationToken cancellationToken) {
|
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunCancellationToken cancellationToken) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
Objects.requireNonNull(fingerprintFilter, "fingerprintFilter must not be null");
|
Objects.requireNonNull(fingerprintFilter, "fingerprintFilter must not be null");
|
||||||
Objects.requireNonNull(progressObserver, "progressObserver must not be null");
|
Objects.requireNonNull(progressObserver, "progressObserver must not be null");
|
||||||
Objects.requireNonNull(cancellationToken, "cancellationToken must not be null");
|
Objects.requireNonNull(cancellationToken, "cancellationToken must not be null");
|
||||||
@@ -1481,7 +1489,7 @@ public class BootstrapRunner {
|
|||||||
LOG.error("GUI-Mini-Verarbeitungslauf: Konfiguration konnte nicht geladen werden: {}",
|
LOG.error("GUI-Mini-Verarbeitungslauf: Konfiguration konnte nicht geladen werden: {}",
|
||||||
e.getMessage(), e);
|
e.getMessage(), e);
|
||||||
return GuiBatchRunLaunchOutcome.rejected(
|
return GuiBatchRunLaunchOutcome.rejected(
|
||||||
"Konfiguration konnte nicht geladen werden: " + e.getMessage());
|
CONFIG_LOAD_FAILED_PREFIX + e.getMessage());
|
||||||
} catch (InvalidStartConfigurationException e) {
|
} catch (InvalidStartConfigurationException e) {
|
||||||
LOG.error("GUI-Mini-Verarbeitungslauf: Konfiguration ist nicht lauffähig: {}", e.getMessage());
|
LOG.error("GUI-Mini-Verarbeitungslauf: Konfiguration ist nicht lauffähig: {}", e.getMessage());
|
||||||
return GuiBatchRunLaunchOutcome.rejected(
|
return GuiBatchRunLaunchOutcome.rejected(
|
||||||
@@ -1533,7 +1541,7 @@ public class BootstrapRunner {
|
|||||||
ResetDocumentStatusResult resetDocumentStatusForGui(
|
ResetDocumentStatusResult resetDocumentStatusForGui(
|
||||||
Path configFilePath,
|
Path configFilePath,
|
||||||
Set<DocumentFingerprint> fingerprints) {
|
Set<DocumentFingerprint> fingerprints) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
Objects.requireNonNull(fingerprints, "fingerprints must not be null");
|
Objects.requireNonNull(fingerprints, "fingerprints must not be null");
|
||||||
LOG.info("GUI-Status-Reset: {} Dokument(e) zurücksetzen, Konfiguration {}.",
|
LOG.info("GUI-Status-Reset: {} Dokument(e) zurücksetzen, Konfiguration {}.",
|
||||||
fingerprints.size(), configFilePath);
|
fingerprints.size(), configFilePath);
|
||||||
@@ -1545,7 +1553,7 @@ public class BootstrapRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!Files.exists(configFilePath)) {
|
if (!Files.exists(configFilePath)) {
|
||||||
String msg = "Konfigurationsdatei nicht gefunden: " + configFilePath;
|
String msg = CONFIG_FILE_NOT_FOUND_PREFIX + configFilePath;
|
||||||
LOG.error("GUI-Status-Reset: {}", msg);
|
LOG.error("GUI-Status-Reset: {}", msg);
|
||||||
return allFailures(fingerprints, msg);
|
return allFailures(fingerprints, msg);
|
||||||
}
|
}
|
||||||
@@ -1587,16 +1595,16 @@ public class BootstrapRunner {
|
|||||||
|
|
||||||
} catch (ConfigurationLoadingException e) {
|
} catch (ConfigurationLoadingException e) {
|
||||||
LOG.error("GUI-Status-Reset: Konfiguration konnte nicht geladen werden: {}", e.getMessage(), e);
|
LOG.error("GUI-Status-Reset: Konfiguration konnte nicht geladen werden: {}", e.getMessage(), e);
|
||||||
return allFailures(fingerprints, "Konfiguration konnte nicht geladen werden: " + e.getMessage());
|
return allFailures(fingerprints, CONFIG_LOAD_FAILED_PREFIX + e.getMessage());
|
||||||
} catch (InvalidStartConfigurationException e) {
|
} catch (InvalidStartConfigurationException e) {
|
||||||
LOG.error("GUI-Status-Reset: Konfiguration ist nicht lauffähig: {}", e.getMessage());
|
LOG.error("GUI-Status-Reset: Konfiguration ist nicht lauffähig: {}", e.getMessage());
|
||||||
return allFailures(fingerprints, "Die Konfiguration ist nicht lauffähig: " + e.getMessage());
|
return allFailures(fingerprints, CONFIG_NOT_RUNNABLE_PREFIX + e.getMessage());
|
||||||
} catch (DocumentPersistenceException e) {
|
} catch (DocumentPersistenceException e) {
|
||||||
LOG.error("GUI-Status-Reset: SQLite-Initialisierung fehlgeschlagen: {}", e.getMessage(), e);
|
LOG.error("GUI-Status-Reset: SQLite-Initialisierung fehlgeschlagen: {}", e.getMessage(), e);
|
||||||
return allFailures(fingerprints, "SQLite-Datenbank konnte nicht vorbereitet werden: " + e.getMessage());
|
return allFailures(fingerprints, "SQLite-Datenbank konnte nicht vorbereitet werden: " + e.getMessage());
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
LOG.error("GUI-Status-Reset: Unerwarteter Fehler: {}", e.getMessage(), e);
|
LOG.error("GUI-Status-Reset: Unerwarteter Fehler: {}", e.getMessage(), e);
|
||||||
return allFailures(fingerprints, "Unerwarteter Fehler: "
|
return allFailures(fingerprints, UNEXPECTED_ERROR_PREFIX
|
||||||
+ (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()));
|
+ (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1684,13 +1692,13 @@ public class BootstrapRunner {
|
|||||||
ManualFileRenameResult performGuiManualFileRename(
|
ManualFileRenameResult performGuiManualFileRename(
|
||||||
Path configFilePath,
|
Path configFilePath,
|
||||||
ManualFileRenameRequest request) {
|
ManualFileRenameRequest request) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
Objects.requireNonNull(request, "request must not be null");
|
Objects.requireNonNull(request, "request must not be null");
|
||||||
LOG.info("GUI-Umbenennung: Anfrage für Fingerprint={}, Zielname={}.",
|
LOG.info("GUI-Umbenennung: Anfrage für Fingerprint={}, Zielname={}.",
|
||||||
request.fingerprint().sha256Hex(), request.desiredBaseFileName());
|
request.fingerprint().sha256Hex(), request.desiredBaseFileName());
|
||||||
|
|
||||||
if (!Files.exists(configFilePath)) {
|
if (!Files.exists(configFilePath)) {
|
||||||
String msg = "Konfigurationsdatei nicht gefunden: " + configFilePath;
|
String msg = CONFIG_FILE_NOT_FOUND_PREFIX + configFilePath;
|
||||||
LOG.error("GUI-Umbenennung: {}", msg);
|
LOG.error("GUI-Umbenennung: {}", msg);
|
||||||
return new de.gecheckt.pdf.umbenenner.application.port.in
|
return new de.gecheckt.pdf.umbenenner.application.port.in
|
||||||
.ManualFileRenameFileSystemFailure(msg);
|
.ManualFileRenameFileSystemFailure(msg);
|
||||||
@@ -1709,12 +1717,12 @@ public class BootstrapRunner {
|
|||||||
e.getMessage(), e);
|
e.getMessage(), e);
|
||||||
return new de.gecheckt.pdf.umbenenner.application.port.in
|
return new de.gecheckt.pdf.umbenenner.application.port.in
|
||||||
.ManualFileRenamePersistenceFailure(
|
.ManualFileRenamePersistenceFailure(
|
||||||
"Konfiguration konnte nicht geladen werden: " + e.getMessage());
|
CONFIG_LOAD_FAILED_PREFIX + e.getMessage());
|
||||||
} catch (InvalidStartConfigurationException e) {
|
} catch (InvalidStartConfigurationException e) {
|
||||||
LOG.error("GUI-Umbenennung: Konfiguration ist nicht lauffähig: {}", e.getMessage());
|
LOG.error("GUI-Umbenennung: Konfiguration ist nicht lauffähig: {}", e.getMessage());
|
||||||
return new de.gecheckt.pdf.umbenenner.application.port.in
|
return new de.gecheckt.pdf.umbenenner.application.port.in
|
||||||
.ManualFileRenamePersistenceFailure(
|
.ManualFileRenamePersistenceFailure(
|
||||||
"Die Konfiguration ist nicht lauffähig: " + e.getMessage());
|
CONFIG_NOT_RUNNABLE_PREFIX + e.getMessage());
|
||||||
} catch (DocumentPersistenceException e) {
|
} catch (DocumentPersistenceException e) {
|
||||||
LOG.error("GUI-Umbenennung: SQLite-Initialisierung fehlgeschlagen: {}",
|
LOG.error("GUI-Umbenennung: SQLite-Initialisierung fehlgeschlagen: {}",
|
||||||
e.getMessage(), e);
|
e.getMessage(), e);
|
||||||
@@ -1725,7 +1733,7 @@ public class BootstrapRunner {
|
|||||||
LOG.error("GUI-Umbenennung: Unerwarteter Fehler: {}", e.getMessage(), e);
|
LOG.error("GUI-Umbenennung: Unerwarteter Fehler: {}", e.getMessage(), e);
|
||||||
return new de.gecheckt.pdf.umbenenner.application.port.in
|
return new de.gecheckt.pdf.umbenenner.application.port.in
|
||||||
.ManualFileRenameFileSystemFailure(
|
.ManualFileRenameFileSystemFailure(
|
||||||
"Unerwarteter Fehler: "
|
UNEXPECTED_ERROR_PREFIX
|
||||||
+ (e.getMessage() == null
|
+ (e.getMessage() == null
|
||||||
? e.getClass().getSimpleName()
|
? e.getClass().getSimpleName()
|
||||||
: e.getMessage()));
|
: e.getMessage()));
|
||||||
@@ -1748,13 +1756,13 @@ public class BootstrapRunner {
|
|||||||
ManualFileCopyResult performGuiManualFileCopy(
|
ManualFileCopyResult performGuiManualFileCopy(
|
||||||
Path configFilePath,
|
Path configFilePath,
|
||||||
ManualFileCopyRequest request) {
|
ManualFileCopyRequest request) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
Objects.requireNonNull(request, "request must not be null");
|
Objects.requireNonNull(request, "request must not be null");
|
||||||
LOG.info("GUI-Dateikopie: Anfrage für Fingerprint={}, Zielname={}.",
|
LOG.info("GUI-Dateikopie: Anfrage für Fingerprint={}, Zielname={}.",
|
||||||
request.fingerprint().sha256Hex(), request.desiredBaseFileName());
|
request.fingerprint().sha256Hex(), request.desiredBaseFileName());
|
||||||
|
|
||||||
if (!Files.exists(configFilePath)) {
|
if (!Files.exists(configFilePath)) {
|
||||||
String msg = "Konfigurationsdatei nicht gefunden: " + configFilePath;
|
String msg = CONFIG_FILE_NOT_FOUND_PREFIX + configFilePath;
|
||||||
LOG.error("GUI-Dateikopie: {}", msg);
|
LOG.error("GUI-Dateikopie: {}", msg);
|
||||||
return new de.gecheckt.pdf.umbenenner.application.port.in
|
return new de.gecheckt.pdf.umbenenner.application.port.in
|
||||||
.ManualFileCopyFileSystemFailure(msg);
|
.ManualFileCopyFileSystemFailure(msg);
|
||||||
@@ -1773,12 +1781,12 @@ public class BootstrapRunner {
|
|||||||
e.getMessage(), e);
|
e.getMessage(), e);
|
||||||
return new de.gecheckt.pdf.umbenenner.application.port.in
|
return new de.gecheckt.pdf.umbenenner.application.port.in
|
||||||
.ManualFileCopyPersistenceFailure(
|
.ManualFileCopyPersistenceFailure(
|
||||||
"Konfiguration konnte nicht geladen werden: " + e.getMessage());
|
CONFIG_LOAD_FAILED_PREFIX + e.getMessage());
|
||||||
} catch (InvalidStartConfigurationException e) {
|
} catch (InvalidStartConfigurationException e) {
|
||||||
LOG.error("GUI-Dateikopie: Konfiguration ist nicht lauffähig: {}", e.getMessage());
|
LOG.error("GUI-Dateikopie: Konfiguration ist nicht lauffähig: {}", e.getMessage());
|
||||||
return new de.gecheckt.pdf.umbenenner.application.port.in
|
return new de.gecheckt.pdf.umbenenner.application.port.in
|
||||||
.ManualFileCopyPersistenceFailure(
|
.ManualFileCopyPersistenceFailure(
|
||||||
"Die Konfiguration ist nicht lauffähig: " + e.getMessage());
|
CONFIG_NOT_RUNNABLE_PREFIX + e.getMessage());
|
||||||
} catch (DocumentPersistenceException e) {
|
} catch (DocumentPersistenceException e) {
|
||||||
LOG.error("GUI-Dateikopie: SQLite-Initialisierung fehlgeschlagen: {}",
|
LOG.error("GUI-Dateikopie: SQLite-Initialisierung fehlgeschlagen: {}",
|
||||||
e.getMessage(), e);
|
e.getMessage(), e);
|
||||||
@@ -1789,7 +1797,7 @@ public class BootstrapRunner {
|
|||||||
LOG.error("GUI-Dateikopie: Unerwarteter Fehler: {}", e.getMessage(), e);
|
LOG.error("GUI-Dateikopie: Unerwarteter Fehler: {}", e.getMessage(), e);
|
||||||
return new de.gecheckt.pdf.umbenenner.application.port.in
|
return new de.gecheckt.pdf.umbenenner.application.port.in
|
||||||
.ManualFileCopyFileSystemFailure(
|
.ManualFileCopyFileSystemFailure(
|
||||||
"Unerwarteter Fehler: "
|
UNEXPECTED_ERROR_PREFIX
|
||||||
+ (e.getMessage() == null
|
+ (e.getMessage() == null
|
||||||
? e.getClass().getSimpleName()
|
? e.getClass().getSimpleName()
|
||||||
: e.getMessage()));
|
: e.getMessage()));
|
||||||
@@ -1814,8 +1822,8 @@ public class BootstrapRunner {
|
|||||||
Optional<HistoricalDocumentContext> resolveHistoricalDocumentContextForGui(
|
Optional<HistoricalDocumentContext> resolveHistoricalDocumentContextForGui(
|
||||||
Path configFilePath,
|
Path configFilePath,
|
||||||
DocumentFingerprint fingerprint) {
|
DocumentFingerprint fingerprint) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
Objects.requireNonNull(fingerprint, FINGERPRINT_NOT_NULL);
|
||||||
|
|
||||||
if (!Files.exists(configFilePath)) {
|
if (!Files.exists(configFilePath)) {
|
||||||
LOG.debug("Historischer Kontext: Konfigurationsdatei nicht gefunden: {}", configFilePath);
|
LOG.debug("Historischer Kontext: Konfigurationsdatei nicht gefunden: {}", configFilePath);
|
||||||
@@ -1853,7 +1861,7 @@ public class BootstrapRunner {
|
|||||||
DefaultHistoryOverviewUseCase.HistoryOverviewResult loadHistoryOverviewForGui(
|
DefaultHistoryOverviewUseCase.HistoryOverviewResult loadHistoryOverviewForGui(
|
||||||
Path configFilePath,
|
Path configFilePath,
|
||||||
de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQuery query) {
|
de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQuery query) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
Objects.requireNonNull(query, "query must not be null");
|
Objects.requireNonNull(query, "query must not be null");
|
||||||
try {
|
try {
|
||||||
migrateConfigurationIfNeeded(configFilePath);
|
migrateConfigurationIfNeeded(configFilePath);
|
||||||
@@ -1883,8 +1891,8 @@ public class BootstrapRunner {
|
|||||||
Optional<DefaultHistoryDetailsUseCase.HistoryDetailsResult> loadHistoryDetailsForGui(
|
Optional<DefaultHistoryDetailsUseCase.HistoryDetailsResult> loadHistoryDetailsForGui(
|
||||||
Path configFilePath,
|
Path configFilePath,
|
||||||
DocumentFingerprint fingerprint) {
|
DocumentFingerprint fingerprint) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
Objects.requireNonNull(fingerprint, FINGERPRINT_NOT_NULL);
|
||||||
try {
|
try {
|
||||||
migrateConfigurationIfNeeded(configFilePath);
|
migrateConfigurationIfNeeded(configFilePath);
|
||||||
StartConfiguration config = loadAndValidateConfiguration(configFilePath);
|
StartConfiguration config = loadAndValidateConfiguration(configFilePath);
|
||||||
@@ -1913,8 +1921,8 @@ public class BootstrapRunner {
|
|||||||
void resetHistoryDocumentStatusForGui(
|
void resetHistoryDocumentStatusForGui(
|
||||||
Path configFilePath,
|
Path configFilePath,
|
||||||
DocumentFingerprint fingerprint) {
|
DocumentFingerprint fingerprint) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
Objects.requireNonNull(fingerprint, FINGERPRINT_NOT_NULL);
|
||||||
LOG.info("Historien-Status-Reset für Fingerprint: {}", fingerprint.sha256Hex());
|
LOG.info("Historien-Status-Reset für Fingerprint: {}", fingerprint.sha256Hex());
|
||||||
try {
|
try {
|
||||||
migrateConfigurationIfNeeded(configFilePath);
|
migrateConfigurationIfNeeded(configFilePath);
|
||||||
@@ -1945,8 +1953,8 @@ public class BootstrapRunner {
|
|||||||
void deleteDocumentHistoryForGui(
|
void deleteDocumentHistoryForGui(
|
||||||
Path configFilePath,
|
Path configFilePath,
|
||||||
DocumentFingerprint fingerprint) {
|
DocumentFingerprint fingerprint) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||||
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
Objects.requireNonNull(fingerprint, FINGERPRINT_NOT_NULL);
|
||||||
LOG.info("Historien-Löschen für Fingerprint: {}", fingerprint.sha256Hex());
|
LOG.info("Historien-Löschen für Fingerprint: {}", fingerprint.sha256Hex());
|
||||||
try {
|
try {
|
||||||
migrateConfigurationIfNeeded(configFilePath);
|
migrateConfigurationIfNeeded(configFilePath);
|
||||||
|
|||||||
+1
@@ -69,6 +69,7 @@ public final class GuiConfigurationPropertiesWriter implements GuiConfigurationF
|
|||||||
* Creates a new properties writer.
|
* Creates a new properties writer.
|
||||||
*/
|
*/
|
||||||
public GuiConfigurationPropertiesWriter() {
|
public GuiConfigurationPropertiesWriter() {
|
||||||
|
// intentionally empty – no initialization required
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+32
-13
@@ -28,6 +28,9 @@ import java.util.Optional;
|
|||||||
* This class is stateless and safe for concurrent use once instantiated.
|
* This class is stateless and safe for concurrent use once instantiated.
|
||||||
*/
|
*/
|
||||||
public class CliArgumentParser {
|
public class CliArgumentParser {
|
||||||
|
private static final String OPTION_PREFIX = "Option ";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final String OPTION_HEADLESS = "--headless";
|
private static final String OPTION_HEADLESS = "--headless";
|
||||||
private static final String OPTION_CONFIG = "--config";
|
private static final String OPTION_CONFIG = "--config";
|
||||||
@@ -82,21 +85,12 @@ public class CliArgumentParser {
|
|||||||
return new StartupArgumentsParseResult.Invalid(
|
return new StartupArgumentsParseResult.Invalid(
|
||||||
"Duplicate option: " + OPTION_CONFIG);
|
"Duplicate option: " + OPTION_CONFIG);
|
||||||
}
|
}
|
||||||
if (i + 1 >= args.length) {
|
StartupArgumentsParseResult validation = validateConfigPathToken(args, i);
|
||||||
return new StartupArgumentsParseResult.Invalid(
|
if (validation != null) {
|
||||||
"Option " + OPTION_CONFIG + " requires a path argument but none was provided");
|
return validation;
|
||||||
}
|
|
||||||
String pathToken = args[i + 1];
|
|
||||||
if (pathToken.startsWith("--")) {
|
|
||||||
return new StartupArgumentsParseResult.Invalid(
|
|
||||||
"Option " + OPTION_CONFIG + " requires a path argument, but got option: " + pathToken);
|
|
||||||
}
|
|
||||||
if (pathToken.isBlank()) {
|
|
||||||
return new StartupArgumentsParseResult.Invalid(
|
|
||||||
"Option " + OPTION_CONFIG + " requires a non-blank path argument");
|
|
||||||
}
|
}
|
||||||
configSeen = true;
|
configSeen = true;
|
||||||
configPath = Optional.of(pathToken);
|
configPath = Optional.of(args[i + 1]);
|
||||||
i += 2;
|
i += 2;
|
||||||
}
|
}
|
||||||
default -> {
|
default -> {
|
||||||
@@ -108,4 +102,29 @@ public class CliArgumentParser {
|
|||||||
|
|
||||||
return new StartupArgumentsParseResult.Valid(new StartupArguments(mode, configPath));
|
return new StartupArgumentsParseResult.Valid(new StartupArguments(mode, configPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that a path token follows the {@code --config} option at position {@code i}.
|
||||||
|
*
|
||||||
|
* @param args the argument array
|
||||||
|
* @param i the index of the {@code --config} token
|
||||||
|
* @return an {@link StartupArgumentsParseResult.Invalid} if the path token is missing or
|
||||||
|
* invalid, {@code null} if the token is acceptable
|
||||||
|
*/
|
||||||
|
private StartupArgumentsParseResult validateConfigPathToken(String[] args, int i) {
|
||||||
|
if (i + 1 >= args.length) {
|
||||||
|
return new StartupArgumentsParseResult.Invalid(
|
||||||
|
OPTION_PREFIX + OPTION_CONFIG + " requires a path argument but none was provided");
|
||||||
|
}
|
||||||
|
String pathToken = args[i + 1];
|
||||||
|
if (pathToken.startsWith("--")) {
|
||||||
|
return new StartupArgumentsParseResult.Invalid(
|
||||||
|
OPTION_PREFIX + OPTION_CONFIG + " requires a path argument, but got option: " + pathToken);
|
||||||
|
}
|
||||||
|
if (pathToken.isBlank()) {
|
||||||
|
return new StartupArgumentsParseResult.Invalid(
|
||||||
|
OPTION_PREFIX + OPTION_CONFIG + " requires a non-blank path argument");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-5
@@ -46,14 +46,25 @@ public final class EarlyLogDirectoryInitializer {
|
|||||||
*/
|
*/
|
||||||
public static void applyFromArgs(String[] args) {
|
public static void applyFromArgs(String[] args) {
|
||||||
try {
|
try {
|
||||||
if (System.getProperty(SYSTEM_PROPERTY_KEY) != null
|
if (isLogPropertyAlreadySet()) {
|
||||||
&& !System.getProperty(SYSTEM_PROPERTY_KEY).isBlank()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Path configPath = resolveConfigPath(args);
|
Path configPath = resolveConfigPath(args);
|
||||||
if (configPath == null || !Files.isRegularFile(configPath)) {
|
if (configPath == null || !Files.isRegularFile(configPath)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
applyLogDirectoryFromConfig(configPath);
|
||||||
|
} catch (IOException | RuntimeException ignored) {
|
||||||
|
// bewusst still: Log4j2-Fallback aus log4j2.xml übernimmt ansonsten
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isLogPropertyAlreadySet() {
|
||||||
|
String val = System.getProperty(SYSTEM_PROPERTY_KEY);
|
||||||
|
return val != null && !val.isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyLogDirectoryFromConfig(Path configPath) throws IOException {
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
try (InputStream in = Files.newInputStream(configPath)) {
|
try (InputStream in = Files.newInputStream(configPath)) {
|
||||||
properties.load(in);
|
properties.load(in);
|
||||||
@@ -62,9 +73,6 @@ public final class EarlyLogDirectoryInitializer {
|
|||||||
if (value != null && !value.isBlank()) {
|
if (value != null && !value.isBlank()) {
|
||||||
System.setProperty(SYSTEM_PROPERTY_KEY, value.trim());
|
System.setProperty(SYSTEM_PROPERTY_KEY, value.trim());
|
||||||
}
|
}
|
||||||
} catch (IOException | RuntimeException ignored) {
|
|
||||||
// bewusst still: Log4j2-Fallback aus log4j2.xml übernimmt ansonsten
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path resolveConfigPath(String[] args) {
|
private static Path resolveConfigPath(String[] args) {
|
||||||
|
|||||||
+9
-3
@@ -450,8 +450,12 @@ class BootstrapRunnerConfigPathSemanticsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class MockRunLockPort implements de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort {
|
private static class MockRunLockPort implements de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort {
|
||||||
@Override public void acquire() {}
|
@Override public void acquire() {
|
||||||
@Override public void release() {}
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void release() {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public java.util.Optional<de.gecheckt.pdf.umbenenner.application.port.out.RunLockHandle> tryAcquire() {
|
public java.util.Optional<de.gecheckt.pdf.umbenenner.application.port.out.RunLockHandle> tryAcquire() {
|
||||||
return java.util.Optional.empty();
|
return java.util.Optional.empty();
|
||||||
@@ -460,7 +464,9 @@ class BootstrapRunnerConfigPathSemanticsTest {
|
|||||||
|
|
||||||
private static class MockSchemaInitializationPort
|
private static class MockSchemaInitializationPort
|
||||||
implements de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitializationPort {
|
implements de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitializationPort {
|
||||||
@Override public void initializeSchema() {}
|
@Override public void initializeSchema() {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MockRunBatchProcessingUseCase
|
private static class MockRunBatchProcessingUseCase
|
||||||
|
|||||||
+6
-2
@@ -567,10 +567,14 @@ class BootstrapRunnerEdgeCasesTest {
|
|||||||
|
|
||||||
private static class MockRunLockPort implements RunLockPort {
|
private static class MockRunLockPort implements RunLockPort {
|
||||||
@Override
|
@Override
|
||||||
public void acquire() { }
|
public void acquire() {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() { }
|
public void release() {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public java.util.Optional<de.gecheckt.pdf.umbenenner.application.port.out.RunLockHandle> tryAcquire() {
|
public java.util.Optional<de.gecheckt.pdf.umbenenner.application.port.out.RunLockHandle> tryAcquire() {
|
||||||
|
|||||||
+9
-3
@@ -256,8 +256,12 @@ class BootstrapRunnerStartupDispatchTest {
|
|||||||
// --- Shared mock helpers (mirroring BootstrapRunnerTest pattern) ---
|
// --- Shared mock helpers (mirroring BootstrapRunnerTest pattern) ---
|
||||||
|
|
||||||
private static class MockRunLockPort implements de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort {
|
private static class MockRunLockPort implements de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort {
|
||||||
@Override public void acquire() {}
|
@Override public void acquire() {
|
||||||
@Override public void release() {}
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void release() {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public java.util.Optional<de.gecheckt.pdf.umbenenner.application.port.out.RunLockHandle> tryAcquire() {
|
public java.util.Optional<de.gecheckt.pdf.umbenenner.application.port.out.RunLockHandle> tryAcquire() {
|
||||||
return java.util.Optional.empty();
|
return java.util.Optional.empty();
|
||||||
@@ -266,7 +270,9 @@ class BootstrapRunnerStartupDispatchTest {
|
|||||||
|
|
||||||
private static class MockSchemaInitializationPort
|
private static class MockSchemaInitializationPort
|
||||||
implements de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitializationPort {
|
implements de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitializationPort {
|
||||||
@Override public void initializeSchema() {}
|
@Override public void initializeSchema() {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MockRunBatchProcessingUseCase
|
private static class MockRunBatchProcessingUseCase
|
||||||
|
|||||||
+6
-2
@@ -546,10 +546,14 @@ class BootstrapRunnerTest {
|
|||||||
|
|
||||||
private static class MockRunLockPort implements RunLockPort {
|
private static class MockRunLockPort implements RunLockPort {
|
||||||
@Override
|
@Override
|
||||||
public void acquire() { }
|
public void acquire() {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() { }
|
public void release() {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public java.util.Optional<de.gecheckt.pdf.umbenenner.application.port.out.RunLockHandle> tryAcquire() {
|
public java.util.Optional<de.gecheckt.pdf.umbenenner.application.port.out.RunLockHandle> tryAcquire() {
|
||||||
|
|||||||
+9
-3
@@ -189,8 +189,12 @@ class BootstrapSmokeTest {
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
private static class NoOpRunLockPort implements RunLockPort {
|
private static class NoOpRunLockPort implements RunLockPort {
|
||||||
@Override public void acquire() { }
|
@Override public void acquire() {
|
||||||
@Override public void release() { }
|
// intentionally empty
|
||||||
|
}
|
||||||
|
@Override public void release() {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public java.util.Optional<de.gecheckt.pdf.umbenenner.application.port.out.RunLockHandle> tryAcquire() {
|
public java.util.Optional<de.gecheckt.pdf.umbenenner.application.port.out.RunLockHandle> tryAcquire() {
|
||||||
return java.util.Optional.empty();
|
return java.util.Optional.empty();
|
||||||
@@ -198,6 +202,8 @@ class BootstrapSmokeTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class NoOpSchemaInitializationPort implements PersistenceSchemaInitializationPort {
|
private static class NoOpSchemaInitializationPort implements PersistenceSchemaInitializationPort {
|
||||||
@Override public void initializeSchema() { }
|
@Override public void initializeSchema() {
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user