Fixe SonarQube Reliability-Issues S2789, S3077 und S2184
S2789 (32 Stellen): null-Checks auf Optional-Feldern entfernt bzw. durch Objects.requireNonNullElse(field, Optional.empty()) ersetzt. Die zuvor defensive Behandlung von null-Optionals erfolgt jetzt ueber den Bibliotheksaufruf, sodass das Verhalten unveraendert bleibt, aber die direkte Null-Pruefung gegen Optional entfaellt. S3077 (5 Stellen): volatile-Felder mit Objekt-Referenzen durch AtomicReference ersetzt (ScheduledExecutorServiceSchedulerAdapter, BootstrapRunner.guiApplicationRunContext, PdfPreviewPane.currentDocument/ currentRenderer/currentSourceFile, SingleInstanceGuard.socket). Die PdfPreviewPane-Felder werden auf JavaFX- bzw. Worker-Thread genutzt; AtomicReference bietet hier konsistente atomare Publikation ohne Verhaltensaenderung. S2184 (3 Stellen): Integer-Division SECONDARY_SPACING / 2 durch SECONDARY_SPACING / 2.0 ersetzt, damit das Insets-Argument als double ohne implizite Truncierung berechnet wird.
This commit is contained in:
+2
-1
@@ -4,6 +4,7 @@ import java.time.Instant;
|
|||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@@ -105,7 +106,7 @@ public final class GuiSchedulerTab {
|
|||||||
public GuiSchedulerTab(
|
public GuiSchedulerTab(
|
||||||
Optional<SchedulerControlUseCase> schedulerUseCase,
|
Optional<SchedulerControlUseCase> schedulerUseCase,
|
||||||
Supplier<Boolean> isConfigDirty) {
|
Supplier<Boolean> isConfigDirty) {
|
||||||
this.schedulerUseCase = schedulerUseCase == null ? Optional.empty() : schedulerUseCase;
|
this.schedulerUseCase = Objects.requireNonNullElse(schedulerUseCase, Optional.empty());
|
||||||
this.isConfigDirty = isConfigDirty != null ? isConfigDirty : () -> false;
|
this.isConfigDirty = isConfigDirty != null ? isConfigDirty : () -> false;
|
||||||
tab.setClosable(false);
|
tab.setClosable(false);
|
||||||
buildUi();
|
buildUi();
|
||||||
|
|||||||
+4
-4
@@ -146,8 +146,8 @@ public record GuiStartupContext(
|
|||||||
*/
|
*/
|
||||||
public GuiStartupContext {
|
public GuiStartupContext {
|
||||||
initialState = Objects.requireNonNull(initialState, "initialState must not be null");
|
initialState = Objects.requireNonNull(initialState, "initialState must not be null");
|
||||||
startupNotice = startupNotice == null ? Optional.empty() : startupNotice;
|
startupNotice = Objects.requireNonNullElse(startupNotice, Optional.empty());
|
||||||
applicationContextError = applicationContextError == null ? Optional.empty() : applicationContextError;
|
applicationContextError = Objects.requireNonNullElse(applicationContextError, Optional.empty());
|
||||||
configurationFileLoader = Objects.requireNonNull(configurationFileLoader,
|
configurationFileLoader = Objects.requireNonNull(configurationFileLoader,
|
||||||
"configurationFileLoader must not be null");
|
"configurationFileLoader must not be null");
|
||||||
configurationFileWriter = Objects.requireNonNull(configurationFileWriter,
|
configurationFileWriter = Objects.requireNonNull(configurationFileWriter,
|
||||||
@@ -191,8 +191,8 @@ public record GuiStartupContext(
|
|||||||
"promptEditorPortFactory must not be null");
|
"promptEditorPortFactory must not be null");
|
||||||
createNewDatabasePort = Objects.requireNonNull(createNewDatabasePort,
|
createNewDatabasePort = Objects.requireNonNull(createNewDatabasePort,
|
||||||
"createNewDatabasePort must not be null");
|
"createNewDatabasePort must not be null");
|
||||||
schedulerControlUseCase = schedulerControlUseCase == null ? Optional.empty() : schedulerControlUseCase;
|
schedulerControlUseCase = Objects.requireNonNullElse(schedulerControlUseCase, Optional.empty());
|
||||||
configurationFileLockPort = configurationFileLockPort == null ? Optional.empty() : configurationFileLockPort;
|
configurationFileLockPort = Objects.requireNonNullElse(configurationFileLockPort, Optional.empty());
|
||||||
applicationContextInitializer = applicationContextInitializer == null
|
applicationContextInitializer = applicationContextInitializer == null
|
||||||
? GuiApplicationContextInitializer.noOp() : applicationContextInitializer;
|
? GuiApplicationContextInitializer.noOp() : applicationContextInitializer;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -276,7 +276,7 @@ public final class GuiBatchRunCoordinator {
|
|||||||
this.historicalDocumentContextPort = Objects.requireNonNull(
|
this.historicalDocumentContextPort = Objects.requireNonNull(
|
||||||
historicalDocumentContextPort, "historicalDocumentContextPort must not be null");
|
historicalDocumentContextPort, "historicalDocumentContextPort must not be null");
|
||||||
this.configurationFileLockPort =
|
this.configurationFileLockPort =
|
||||||
configurationFileLockPort == null ? Optional.empty() : configurationFileLockPort;
|
Objects.requireNonNullElse(configurationFileLockPort, Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ public record GuiBatchRunLaunchOutcome(
|
|||||||
* Compact constructor normalising the failure message holder.
|
* Compact constructor normalising the failure message holder.
|
||||||
*/
|
*/
|
||||||
public GuiBatchRunLaunchOutcome {
|
public GuiBatchRunLaunchOutcome {
|
||||||
failureMessage = failureMessage == null ? Optional.empty() : failureMessage;
|
failureMessage = Objects.requireNonNullElse(failureMessage, Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+6
-6
@@ -88,16 +88,16 @@ public record GuiBatchRunResultRow(
|
|||||||
}
|
}
|
||||||
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
||||||
Objects.requireNonNull(status, "status must not be null");
|
Objects.requireNonNull(status, "status must not be null");
|
||||||
finalFileName = finalFileName == null ? Optional.empty() : finalFileName;
|
finalFileName = Objects.requireNonNullElse(finalFileName, Optional.empty());
|
||||||
correctedFileName = correctedFileName == null ? Optional.empty() : correctedFileName;
|
correctedFileName = Objects.requireNonNullElse(correctedFileName, Optional.empty());
|
||||||
resolvedDate = resolvedDate == null ? Optional.empty() : resolvedDate;
|
resolvedDate = Objects.requireNonNullElse(resolvedDate, Optional.empty());
|
||||||
aiReasoning = aiReasoning == null ? Optional.empty() : aiReasoning;
|
aiReasoning = Objects.requireNonNullElse(aiReasoning, Optional.empty());
|
||||||
aiFailureMessage = aiFailureMessage == null ? Optional.empty() : aiFailureMessage;
|
aiFailureMessage = Objects.requireNonNullElse(aiFailureMessage, Optional.empty());
|
||||||
Objects.requireNonNull(processingDuration, "processingDuration must not be null");
|
Objects.requireNonNull(processingDuration, "processingDuration must not be null");
|
||||||
if (processingDuration.isNegative()) {
|
if (processingDuration.isNegative()) {
|
||||||
throw new IllegalArgumentException("processingDuration must not be negative");
|
throw new IllegalArgumentException("processingDuration must not be negative");
|
||||||
}
|
}
|
||||||
historicalContext = historicalContext == null ? Optional.empty() : historicalContext;
|
historicalContext = Objects.requireNonNullElse(historicalContext, Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+3
-3
@@ -302,7 +302,7 @@ public final class GuiBatchRunTab {
|
|||||||
targetFolderSupplier, "targetFolderSupplier must not be null");
|
targetFolderSupplier, "targetFolderSupplier must not be null");
|
||||||
|
|
||||||
Optional<ConfigurationFileLockPort> effectiveLockPort =
|
Optional<ConfigurationFileLockPort> effectiveLockPort =
|
||||||
configurationFileLockPort == null ? Optional.empty() : configurationFileLockPort;
|
Objects.requireNonNullElse(configurationFileLockPort, Optional.empty());
|
||||||
this.coordinator = new GuiBatchRunCoordinator(
|
this.coordinator = new GuiBatchRunCoordinator(
|
||||||
(configPath, observer, token) ->
|
(configPath, observer, token) ->
|
||||||
launcherSupplier.get().launch(configPath, observer, token),
|
launcherSupplier.get().launch(configPath, observer, token),
|
||||||
@@ -618,7 +618,7 @@ public final class GuiBatchRunTab {
|
|||||||
|
|
||||||
HBox selectionButtonBar = new HBox(SECONDARY_SPACING, reprocessButton, resetStatusButton);
|
HBox selectionButtonBar = new HBox(SECONDARY_SPACING, reprocessButton, resetStatusButton);
|
||||||
selectionButtonBar.setAlignment(Pos.CENTER_LEFT);
|
selectionButtonBar.setAlignment(Pos.CENTER_LEFT);
|
||||||
selectionButtonBar.setPadding(new Insets(SECONDARY_SPACING / 2, 0, SECONDARY_SPACING / 2, 0));
|
selectionButtonBar.setPadding(new Insets(SECONDARY_SPACING / 2.0, 0, SECONDARY_SPACING / 2.0, 0));
|
||||||
|
|
||||||
// Meldungsbereich unterhalb der Selektions-Buttons (linke Spalte)
|
// Meldungsbereich unterhalb der Selektions-Buttons (linke Spalte)
|
||||||
messageArea.setId("batch-run-message-area");
|
messageArea.setId("batch-run-message-area");
|
||||||
@@ -1246,7 +1246,7 @@ public final class GuiBatchRunTab {
|
|||||||
|
|
||||||
HBox runButtonBar = new HBox(SECONDARY_SPACING, startButton, cancelButton);
|
HBox runButtonBar = new HBox(SECONDARY_SPACING, startButton, cancelButton);
|
||||||
runButtonBar.setAlignment(Pos.CENTER_LEFT);
|
runButtonBar.setAlignment(Pos.CENTER_LEFT);
|
||||||
runButtonBar.setPadding(new Insets(SECONDARY_SPACING / 2, 0, 0, 0));
|
runButtonBar.setPadding(new Insets(SECONDARY_SPACING / 2.0, 0, 0, 0));
|
||||||
|
|
||||||
return runButtonBar;
|
return runButtonBar;
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-16
@@ -8,6 +8,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@@ -170,18 +171,18 @@ public final class PdfPreviewPane {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Aktuell geöffnetes PDF-Dokument. Zugriff ausschließlich vom Worker-Thread.
|
* Aktuell geöffnetes PDF-Dokument. Zugriff ausschließlich vom Worker-Thread.
|
||||||
* {@code null} wenn kein Dokument geöffnet ist.
|
* Leerer Referenzwert wenn kein Dokument geöffnet ist.
|
||||||
*/
|
*/
|
||||||
private volatile PDDocument currentDocument = null;
|
private final AtomicReference<PDDocument> currentDocument = new AtomicReference<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renderer für das aktuell geöffnete Dokument. Zugriff ausschließlich vom Worker-Thread.
|
* Renderer für das aktuell geöffnete Dokument. Zugriff ausschließlich vom Worker-Thread.
|
||||||
* {@code null} wenn kein Dokument geöffnet ist.
|
* Leerer Referenzwert wenn kein Dokument geöffnet ist.
|
||||||
*/
|
*/
|
||||||
private volatile PDFRenderer currentRenderer = null;
|
private final AtomicReference<PDFRenderer> currentRenderer = new AtomicReference<>();
|
||||||
|
|
||||||
/** Aktuell geladene Quelldatei; null wenn keine Selektion vorliegt. */
|
/** Aktuell geladene Quelldatei; leerer Referenzwert wenn keine Selektion vorliegt. */
|
||||||
private volatile Path currentSourceFile = null;
|
private final AtomicReference<Path> currentSourceFile = new AtomicReference<>();
|
||||||
|
|
||||||
/** Aktuell angezeigte Seite (1-basiert; 0 wenn keine Datei geladen). */
|
/** Aktuell angezeigte Seite (1-basiert; 0 wenn keine Datei geladen). */
|
||||||
private volatile int currentPage = 0;
|
private volatile int currentPage = 0;
|
||||||
@@ -303,7 +304,7 @@ public final class PdfPreviewPane {
|
|||||||
clear();
|
clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentSourceFile = sourceFile;
|
currentSourceFile.set(sourceFile);
|
||||||
currentPage = 0;
|
currentPage = 0;
|
||||||
totalPages = -1;
|
totalPages = -1;
|
||||||
pageCache.clear();
|
pageCache.clear();
|
||||||
@@ -318,7 +319,7 @@ public final class PdfPreviewPane {
|
|||||||
* Muss auf dem JavaFX Application Thread aufgerufen werden.
|
* Muss auf dem JavaFX Application Thread aufgerufen werden.
|
||||||
*/
|
*/
|
||||||
public void clear() {
|
public void clear() {
|
||||||
currentSourceFile = null;
|
currentSourceFile.set(null);
|
||||||
currentPage = 0;
|
currentPage = 0;
|
||||||
totalPages = -1;
|
totalPages = -1;
|
||||||
pageCache.clear();
|
pageCache.clear();
|
||||||
@@ -472,12 +473,13 @@ public final class PdfPreviewPane {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
PDDocument doc = Loader.loadPDF(ioFile);
|
PDDocument doc = Loader.loadPDF(ioFile);
|
||||||
currentDocument = doc;
|
currentDocument.set(doc);
|
||||||
currentRenderer = new PDFRenderer(doc);
|
PDFRenderer renderer = new PDFRenderer(doc);
|
||||||
|
currentRenderer.set(renderer);
|
||||||
|
|
||||||
int pages = Math.max(1, doc.getNumberOfPages());
|
int pages = Math.max(1, doc.getNumberOfPages());
|
||||||
BufferedImage buffered =
|
BufferedImage buffered =
|
||||||
currentRenderer.renderImageWithDPI(0, RENDER_DPI, ImageType.RGB);
|
renderer.renderImageWithDPI(0, RENDER_DPI, ImageType.RGB);
|
||||||
Image fxImage = SwingFXUtils.toFXImage(buffered, null);
|
Image fxImage = SwingFXUtils.toFXImage(buffered, null);
|
||||||
|
|
||||||
final int totalPagesFinal = pages;
|
final int totalPagesFinal = pages;
|
||||||
@@ -513,7 +515,7 @@ public final class PdfPreviewPane {
|
|||||||
* @param seq die Sequenznummer dieser Anforderung
|
* @param seq die Sequenznummer dieser Anforderung
|
||||||
*/
|
*/
|
||||||
private void renderPageOnWorker(int page, long seq) {
|
private void renderPageOnWorker(int page, long seq) {
|
||||||
PDFRenderer renderer = currentRenderer;
|
PDFRenderer renderer = currentRenderer.get();
|
||||||
if (renderer == null) {
|
if (renderer == null) {
|
||||||
// Dokument wurde zwischenzeitlich geschlossen – nichts zu tun
|
// Dokument wurde zwischenzeitlich geschlossen – nichts zu tun
|
||||||
return;
|
return;
|
||||||
@@ -542,9 +544,8 @@ public final class PdfPreviewPane {
|
|||||||
* auf dem Worker-Thread und ist idempotent.
|
* auf dem Worker-Thread und ist idempotent.
|
||||||
*/
|
*/
|
||||||
private void closeCurrentDocumentOnWorker() {
|
private void closeCurrentDocumentOnWorker() {
|
||||||
PDDocument doc = currentDocument;
|
PDDocument doc = currentDocument.getAndSet(null);
|
||||||
currentDocument = null;
|
currentRenderer.set(null);
|
||||||
currentRenderer = null;
|
|
||||||
if (doc != null) {
|
if (doc != null) {
|
||||||
try {
|
try {
|
||||||
doc.close();
|
doc.close();
|
||||||
@@ -824,7 +825,7 @@ public final class PdfPreviewPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateNavigationButtons() {
|
private void updateNavigationButtons() {
|
||||||
boolean canNavigate = enabled && currentSourceFile != null && totalPages > 0;
|
boolean canNavigate = enabled && currentSourceFile.get() != null && totalPages > 0;
|
||||||
prevButton.setDisable(!canNavigate || currentPage <= 1);
|
prevButton.setDisable(!canNavigate || currentPage <= 1);
|
||||||
nextButton.setDisable(!canNavigate || currentPage >= totalPages);
|
nextButton.setDisable(!canNavigate || currentPage >= totalPages);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -29,10 +29,10 @@ public record GuiConfigurationEditorState(
|
|||||||
* @param values current editable configuration values; must not be {@code null}
|
* @param values current editable configuration values; must not be {@code null}
|
||||||
*/
|
*/
|
||||||
public GuiConfigurationEditorState {
|
public GuiConfigurationEditorState {
|
||||||
loadedFileSnapshot = loadedFileSnapshot == null ? Optional.empty() : loadedFileSnapshot;
|
loadedFileSnapshot = Objects.requireNonNullElse(loadedFileSnapshot, Optional.empty());
|
||||||
baselineValues = Objects.requireNonNull(baselineValues, "baselineValues must not be null");
|
baselineValues = Objects.requireNonNull(baselineValues, "baselineValues must not be null");
|
||||||
values = Objects.requireNonNull(values, "values must not be null");
|
values = Objects.requireNonNull(values, "values must not be null");
|
||||||
pendingMigrationMessage = pendingMigrationMessage == null ? Optional.empty() : pendingMigrationMessage;
|
pendingMigrationMessage = Objects.requireNonNullElse(pendingMigrationMessage, Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ public record GuiMessageEntry(
|
|||||||
Objects.requireNonNull(severity, "severity must not be null");
|
Objects.requireNonNull(severity, "severity must not be null");
|
||||||
Objects.requireNonNull(text, "text must not be null");
|
Objects.requireNonNull(text, "text must not be null");
|
||||||
Objects.requireNonNull(timestamp, "timestamp must not be null");
|
Objects.requireNonNull(timestamp, "timestamp must not be null");
|
||||||
source = source == null ? Optional.empty() : source;
|
source = Objects.requireNonNullElse(source, Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+4
-5
@@ -54,7 +54,7 @@ public class ScheduledExecutorServiceSchedulerAdapter implements SchedulerPort {
|
|||||||
*/
|
*/
|
||||||
final AtomicReference<BatchRunTrigger> currentTrigger = new AtomicReference<>();
|
final AtomicReference<BatchRunTrigger> currentTrigger = new AtomicReference<>();
|
||||||
|
|
||||||
private volatile ScheduledExecutorService executor;
|
private final AtomicReference<ScheduledExecutorService> executor = new AtomicReference<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt einen neuen Adapter.
|
* Erstellt einen neuen Adapter.
|
||||||
@@ -85,7 +85,7 @@ public class ScheduledExecutorServiceSchedulerAdapter implements SchedulerPort {
|
|||||||
public void startScheduler(SchedulerConfig config, BatchRunTrigger trigger) {
|
public void startScheduler(SchedulerConfig config, BatchRunTrigger trigger) {
|
||||||
Objects.requireNonNull(config, "config darf nicht null sein");
|
Objects.requireNonNull(config, "config darf nicht null sein");
|
||||||
Objects.requireNonNull(trigger, "trigger darf nicht null sein");
|
Objects.requireNonNull(trigger, "trigger darf nicht null sein");
|
||||||
if (executor != null) {
|
if (executor.get() != null) {
|
||||||
logger.debug("Scheduler ist bereits aktiv – Start-Aufruf wird ignoriert.");
|
logger.debug("Scheduler ist bereits aktiv – Start-Aufruf wird ignoriert.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ public class ScheduledExecutorServiceSchedulerAdapter implements SchedulerPort {
|
|||||||
0L,
|
0L,
|
||||||
config.intervalSeconds(),
|
config.intervalSeconds(),
|
||||||
TimeUnit.SECONDS);
|
TimeUnit.SECONDS);
|
||||||
executor = newExecutor;
|
executor.set(newExecutor);
|
||||||
logger.info("Scheduler gestartet. Intervall: {} Sekunden.", config.intervalSeconds());
|
logger.info("Scheduler gestartet. Intervall: {} Sekunden.", config.intervalSeconds());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,12 +118,11 @@ public class ScheduledExecutorServiceSchedulerAdapter implements SchedulerPort {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void stopScheduler() {
|
public void stopScheduler() {
|
||||||
ScheduledExecutorService localExecutor = executor;
|
ScheduledExecutorService localExecutor = executor.getAndSet(null);
|
||||||
if (localExecutor == null) {
|
if (localExecutor == null) {
|
||||||
logger.debug("Scheduler ist bereits gestoppt – Stop-Aufruf wird ignoriert.");
|
logger.debug("Scheduler ist bereits gestoppt – Stop-Aufruf wird ignoriert.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
executor = null;
|
|
||||||
currentTrigger.set(null);
|
currentTrigger.set(null);
|
||||||
localExecutor.shutdown();
|
localExecutor.shutdown();
|
||||||
logger.info("Scheduler angehalten.");
|
logger.info("Scheduler angehalten.");
|
||||||
|
|||||||
+4
-3
@@ -1,6 +1,7 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.application.port.in;
|
package de.gecheckt.pdf.umbenenner.application.port.in;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,9 +38,9 @@ public record HistoricalDocumentContext(
|
|||||||
* {@code lastFailureInstant} {@code null} sind
|
* {@code lastFailureInstant} {@code null} sind
|
||||||
*/
|
*/
|
||||||
public HistoricalDocumentContext {
|
public HistoricalDocumentContext {
|
||||||
lastTargetFileName = lastTargetFileName == null ? Optional.empty() : lastTargetFileName;
|
lastTargetFileName = Objects.requireNonNullElse(lastTargetFileName, Optional.empty());
|
||||||
lastSuccessInstant = lastSuccessInstant == null ? Optional.empty() : lastSuccessInstant;
|
lastSuccessInstant = Objects.requireNonNullElse(lastSuccessInstant, Optional.empty());
|
||||||
lastFailureInstant = lastFailureInstant == null ? Optional.empty() : lastFailureInstant;
|
lastFailureInstant = Objects.requireNonNullElse(lastFailureInstant, Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+5
-12
@@ -1,6 +1,7 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.application.port.in;
|
package de.gecheckt.pdf.umbenenner.application.port.in;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunSummary;
|
import de.gecheckt.pdf.umbenenner.application.port.out.RunSummary;
|
||||||
@@ -48,18 +49,10 @@ public record SchedulerStatus(
|
|||||||
if (state == null) {
|
if (state == null) {
|
||||||
throw new IllegalArgumentException("state darf nicht null sein");
|
throw new IllegalArgumentException("state darf nicht null sein");
|
||||||
}
|
}
|
||||||
if (lastRunEndedAt == null) {
|
Objects.requireNonNull(lastRunEndedAt, "lastRunEndedAt darf nicht null sein");
|
||||||
throw new IllegalArgumentException("lastRunEndedAt darf nicht null sein");
|
Objects.requireNonNull(lastRunSummary, "lastRunSummary darf nicht null sein");
|
||||||
}
|
Objects.requireNonNull(nextTickAt, "nextTickAt darf nicht null sein");
|
||||||
if (lastRunSummary == null) {
|
Objects.requireNonNull(lastError, "lastError darf nicht null sein");
|
||||||
throw new IllegalArgumentException("lastRunSummary darf nicht null sein");
|
|
||||||
}
|
|
||||||
if (nextTickAt == null) {
|
|
||||||
throw new IllegalArgumentException("nextTickAt darf nicht null sein");
|
|
||||||
}
|
|
||||||
if (lastError == null) {
|
|
||||||
throw new IllegalArgumentException("lastError darf nicht null sein");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ public record EffectiveApiKeyDescriptor(
|
|||||||
*/
|
*/
|
||||||
public EffectiveApiKeyDescriptor {
|
public EffectiveApiKeyDescriptor {
|
||||||
Objects.requireNonNull(origin, "origin must not be null");
|
Objects.requireNonNull(origin, "origin must not be null");
|
||||||
envVarName = envVarName == null ? Optional.empty() : envVarName;
|
envVarName = Objects.requireNonNullElse(envVarName, Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+2
-2
@@ -45,7 +45,7 @@ public record ModelCatalogRequest(
|
|||||||
if (timeoutSeconds <= 0) {
|
if (timeoutSeconds <= 0) {
|
||||||
throw new IllegalArgumentException("timeoutSeconds must be positive, was: " + timeoutSeconds);
|
throw new IllegalArgumentException("timeoutSeconds must be positive, was: " + timeoutSeconds);
|
||||||
}
|
}
|
||||||
baseUrl = baseUrl == null ? Optional.empty() : baseUrl;
|
baseUrl = Objects.requireNonNullElse(baseUrl, Optional.empty());
|
||||||
apiKey = apiKey == null ? Optional.empty() : apiKey;
|
apiKey = Objects.requireNonNullElse(apiKey, Optional.empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ public record EditorValidationFinding(
|
|||||||
public EditorValidationFinding {
|
public EditorValidationFinding {
|
||||||
Objects.requireNonNull(severity, "severity must not be null");
|
Objects.requireNonNull(severity, "severity must not be null");
|
||||||
Objects.requireNonNull(message, "message must not be null");
|
Objects.requireNonNull(message, "message must not be null");
|
||||||
fieldKey = fieldKey == null ? Optional.empty() : fieldKey;
|
fieldKey = Objects.requireNonNullElse(fieldKey, Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-3
@@ -89,9 +89,7 @@ public sealed interface CheckpointResult
|
|||||||
Objects.requireNonNull(checkpointId, "checkpointId must not be null");
|
Objects.requireNonNull(checkpointId, "checkpointId must not be null");
|
||||||
Objects.requireNonNull(severity, "severity must not be null");
|
Objects.requireNonNull(severity, "severity must not be null");
|
||||||
Objects.requireNonNull(message, "message must not be null");
|
Objects.requireNonNull(message, "message must not be null");
|
||||||
correctionSuggestion = correctionSuggestion == null
|
correctionSuggestion = Objects.requireNonNullElse(correctionSuggestion, Optional.empty());
|
||||||
? Optional.empty()
|
|
||||||
: correctionSuggestion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+17
-15
@@ -13,6 +13,7 @@ import java.util.Optional;
|
|||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@@ -260,10 +261,11 @@ public class BootstrapRunner {
|
|||||||
* or via the {@link GuiApplicationContextInitializer} callback that the workspace invokes
|
* or via the {@link GuiApplicationContextInitializer} callback that the workspace invokes
|
||||||
* on a background thread after each successful file open. Read by
|
* on a background thread after each successful file open. Read by
|
||||||
* {@link #launchGuiBatchRun}, {@link #launchGuiMiniBatchRun}, and
|
* {@link #launchGuiBatchRun}, {@link #launchGuiMiniBatchRun}, and
|
||||||
* {@link #resetDocumentStatusForGui}. {@code volatile} ensures visibility across threads
|
* {@link #resetDocumentStatusForGui}. {@link AtomicReference} ensures atomic publication
|
||||||
* without explicit synchronisation on the happy path.
|
* of the context across threads.
|
||||||
*/
|
*/
|
||||||
private volatile Optional<ApplicationRunContext> guiApplicationRunContext = Optional.empty();
|
private final AtomicReference<Optional<ApplicationRunContext>> guiApplicationRunContext =
|
||||||
|
new AtomicReference<>(Optional.empty());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Der Scheduler-Use-Case, der beim GUI-Start mit einer gültigen Konfiguration
|
* Der Scheduler-Use-Case, der beim GUI-Start mit einer gültigen Konfiguration
|
||||||
@@ -1083,7 +1085,7 @@ public class BootstrapRunner {
|
|||||||
result -> LOG.debug("Scheduler-Tick-Ergebnis: {}",
|
result -> LOG.debug("Scheduler-Tick-Ergebnis: {}",
|
||||||
result.getClass().getSimpleName()));
|
result.getClass().getSimpleName()));
|
||||||
BatchRunTrigger batchRunTrigger = () -> {
|
BatchRunTrigger batchRunTrigger = () -> {
|
||||||
Optional<ApplicationRunContext> ctxOpt = guiApplicationRunContext;
|
Optional<ApplicationRunContext> ctxOpt = guiApplicationRunContext.get();
|
||||||
if (ctxOpt.isEmpty()) {
|
if (ctxOpt.isEmpty()) {
|
||||||
return new BatchRunTriggerResult.Failed(
|
return new BatchRunTriggerResult.Failed(
|
||||||
"Kein Anwendungskontext verfügbar.",
|
"Kein Anwendungskontext verfügbar.",
|
||||||
@@ -1248,28 +1250,28 @@ public class BootstrapRunner {
|
|||||||
migrateConfigurationIfNeeded(configFilePath);
|
migrateConfigurationIfNeeded(configFilePath);
|
||||||
StartConfiguration config = loadAndValidateConfiguration(configFilePath);
|
StartConfiguration config = loadAndValidateConfiguration(configFilePath);
|
||||||
initializeSchema(config);
|
initializeSchema(config);
|
||||||
guiApplicationRunContext = Optional.of(
|
guiApplicationRunContext.set(Optional.of(
|
||||||
new ApplicationRunContext(config, resolveActiveJdbcUrl(config)));
|
new ApplicationRunContext(config, resolveActiveJdbcUrl(config))));
|
||||||
LOG.info("GUI-Anwendungskontext initialisiert für Konfiguration: {}", configFilePath);
|
LOG.info("GUI-Anwendungskontext initialisiert für Konfiguration: {}", configFilePath);
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
} catch (ConfigurationLoadingException e) {
|
} catch (ConfigurationLoadingException e) {
|
||||||
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.set(Optional.empty());
|
||||||
return Optional.of(CONFIG_LOAD_FAILED_PREFIX + 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.set(Optional.empty());
|
||||||
return Optional.of("Konfiguration nicht lauffähig: " + e.getMessage());
|
return Optional.of("Konfiguration nicht lauffähig: " + e.getMessage());
|
||||||
} catch (DocumentPersistenceException e) {
|
} catch (DocumentPersistenceException e) {
|
||||||
LOG.warn("GUI-Anwendungskontext: SQLite-Initialisierung fehlgeschlagen: {}",
|
LOG.warn("GUI-Anwendungskontext: SQLite-Initialisierung fehlgeschlagen: {}",
|
||||||
e.getMessage());
|
e.getMessage());
|
||||||
guiApplicationRunContext = Optional.empty();
|
guiApplicationRunContext.set(Optional.empty());
|
||||||
return Optional.of("SQLite konnte nicht initialisiert werden: " + e.getMessage());
|
return Optional.of("SQLite konnte nicht initialisiert werden: " + e.getMessage());
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
LOG.warn("GUI-Anwendungskontext: Unerwarteter Fehler bei Initialisierung: {}",
|
LOG.warn("GUI-Anwendungskontext: Unerwarteter Fehler bei Initialisierung: {}",
|
||||||
e.getMessage());
|
e.getMessage());
|
||||||
guiApplicationRunContext = Optional.empty();
|
guiApplicationRunContext.set(Optional.empty());
|
||||||
return Optional.of("Unerwarteter Fehler bei der Kontextinitialisierung: "
|
return Optional.of("Unerwarteter Fehler bei der Kontextinitialisierung: "
|
||||||
+ (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()));
|
+ (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()));
|
||||||
}
|
}
|
||||||
@@ -1422,7 +1424,7 @@ public class BootstrapRunner {
|
|||||||
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);
|
||||||
|
|
||||||
Optional<ApplicationRunContext> ctx = guiApplicationRunContext;
|
Optional<ApplicationRunContext> ctx = guiApplicationRunContext.get();
|
||||||
if (ctx.isPresent()) {
|
if (ctx.isPresent()) {
|
||||||
LOG.debug("GUI-Verarbeitungslauf: Verwende vorbereiteten Anwendungskontext.");
|
LOG.debug("GUI-Verarbeitungslauf: Verwende vorbereiteten Anwendungskontext.");
|
||||||
return executeRun(ctx.get(), progressObserver, cancellationToken);
|
return executeRun(ctx.get(), progressObserver, cancellationToken);
|
||||||
@@ -1512,7 +1514,7 @@ public class BootstrapRunner {
|
|||||||
LOG.info("GUI-Mini-Verarbeitungslauf: Startanforderung für {} Dokument(e), Konfiguration {}.",
|
LOG.info("GUI-Mini-Verarbeitungslauf: Startanforderung für {} Dokument(e), Konfiguration {}.",
|
||||||
fingerprintFilter.size(), configFilePath);
|
fingerprintFilter.size(), configFilePath);
|
||||||
|
|
||||||
Optional<ApplicationRunContext> ctx = guiApplicationRunContext;
|
Optional<ApplicationRunContext> ctx = guiApplicationRunContext.get();
|
||||||
if (ctx.isPresent()) {
|
if (ctx.isPresent()) {
|
||||||
LOG.debug("GUI-Mini-Verarbeitungslauf: Verwende vorbereiteten Anwendungskontext.");
|
LOG.debug("GUI-Mini-Verarbeitungslauf: Verwende vorbereiteten Anwendungskontext.");
|
||||||
return executeRunWithFilter(ctx.get(), fingerprintFilter, progressObserver, cancellationToken);
|
return executeRunWithFilter(ctx.get(), fingerprintFilter, progressObserver, cancellationToken);
|
||||||
@@ -1595,7 +1597,7 @@ public class BootstrapRunner {
|
|||||||
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);
|
||||||
|
|
||||||
Optional<ApplicationRunContext> ctx = guiApplicationRunContext;
|
Optional<ApplicationRunContext> ctx = guiApplicationRunContext.get();
|
||||||
if (ctx.isPresent()) {
|
if (ctx.isPresent()) {
|
||||||
LOG.debug("GUI-Status-Reset: Verwende vorbereiteten Anwendungskontext.");
|
LOG.debug("GUI-Status-Reset: Verwende vorbereiteten Anwendungskontext.");
|
||||||
return executeResetWithContext(ctx.get(), fingerprints);
|
return executeResetWithContext(ctx.get(), fingerprints);
|
||||||
@@ -2181,7 +2183,7 @@ public class BootstrapRunner {
|
|||||||
* @return aktive JDBC-URL; nie {@code null}
|
* @return aktive JDBC-URL; nie {@code null}
|
||||||
*/
|
*/
|
||||||
private String resolveJdbcUrlForGui(Path configFilePath) {
|
private String resolveJdbcUrlForGui(Path configFilePath) {
|
||||||
Optional<ApplicationRunContext> ctx = guiApplicationRunContext;
|
Optional<ApplicationRunContext> ctx = guiApplicationRunContext.get();
|
||||||
if (ctx.isPresent()) {
|
if (ctx.isPresent()) {
|
||||||
return ctx.get().jdbcUrl();
|
return ctx.get().jdbcUrl();
|
||||||
}
|
}
|
||||||
@@ -2204,7 +2206,7 @@ public class BootstrapRunner {
|
|||||||
* @return validierte Konfiguration; nie {@code null}
|
* @return validierte Konfiguration; nie {@code null}
|
||||||
*/
|
*/
|
||||||
private StartConfiguration resolveStartConfigurationForGui(Path configFilePath) {
|
private StartConfiguration resolveStartConfigurationForGui(Path configFilePath) {
|
||||||
Optional<ApplicationRunContext> ctx = guiApplicationRunContext;
|
Optional<ApplicationRunContext> ctx = guiApplicationRunContext.get();
|
||||||
if (ctx.isPresent()) {
|
if (ctx.isPresent()) {
|
||||||
return ctx.get().startConfiguration();
|
return ctx.get().startConfiguration();
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -5,6 +5,7 @@ import java.net.BindException;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prozessweiter Einzelinstanz-Schutz auf Basis eines Loopback-ServerSocket-Binds.
|
* Prozessweiter Einzelinstanz-Schutz auf Basis eines Loopback-ServerSocket-Binds.
|
||||||
@@ -44,7 +45,7 @@ public class SingleInstanceGuard implements AutoCloseable {
|
|||||||
public static final int DEFAULT_PORT = 47832;
|
public static final int DEFAULT_PORT = 47832;
|
||||||
|
|
||||||
private final int port;
|
private final int port;
|
||||||
private volatile ServerSocket socket;
|
private final AtomicReference<ServerSocket> socket = new AtomicReference<>();
|
||||||
private final AtomicBoolean closed = new AtomicBoolean(false);
|
private final AtomicBoolean closed = new AtomicBoolean(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,7 +86,7 @@ public class SingleInstanceGuard implements AutoCloseable {
|
|||||||
try {
|
try {
|
||||||
InetAddress loopback = InetAddress.getLoopbackAddress();
|
InetAddress loopback = InetAddress.getLoopbackAddress();
|
||||||
ServerSocket serverSocket = new ServerSocket(port, 1, loopback);
|
ServerSocket serverSocket = new ServerSocket(port, 1, loopback);
|
||||||
this.socket = serverSocket;
|
this.socket.set(serverSocket);
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> schliesseSilent(serverSocket),
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> schliesseSilent(serverSocket),
|
||||||
"single-instance-guard-shutdown"));
|
"single-instance-guard-shutdown"));
|
||||||
} catch (BindException e) {
|
} catch (BindException e) {
|
||||||
@@ -106,7 +107,7 @@ public class SingleInstanceGuard implements AutoCloseable {
|
|||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (closed.compareAndSet(false, true)) {
|
if (closed.compareAndSet(false, true)) {
|
||||||
schliesseSilent(socket);
|
schliesseSilent(socket.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user