Bootstrap-Wiring: Scheduler in GUI-Startkontext verdrahten
- pdf-umbenenner-bootstrap/pom.xml: Abhängigkeit auf adapter-in-scheduler hinzugefügt - GuiStartupContext: neues Feld schedulerControlUseCase (Optional<SchedulerControlUseCase>) als 26. Record-Komponente; 25-Parameter-Backward-Compat-Konstruktor sichert Abwärtskompatibilität - DefaultSchedulerControlUseCase: öffentliche Methode markAutostartFailed() ergänzt - BootstrapRunner: guiSchedulerUseCase-Feld, tryInitializeScheduler(), stopGuiSchedulerIfActive() sowie BatchRunTrigger-Lambda; Autostart gemäß scheduler.enabled-Konfiguration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+79
-3
@@ -18,6 +18,7 @@ import de.gecheckt.pdf.umbenenner.adapter.in.gui.history.GuiHistoryResetDocument
|
|||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationEditorState;
|
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationEditorState;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationEditorStateFactory;
|
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationEditorStateFactory;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.ResetDocumentStatusResult;
|
import de.gecheckt.pdf.umbenenner.application.port.in.ResetDocumentStatusResult;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.AiModelCatalogPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.AiModelCatalogPort;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor;
|
import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor;
|
||||||
import de.gecheckt.pdf.umbenenner.application.validation.editor.ApiKeyResolutionPort;
|
import de.gecheckt.pdf.umbenenner.application.validation.editor.ApiKeyResolutionPort;
|
||||||
@@ -48,14 +49,19 @@ import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
|||||||
* the {@link GuiManualFileCopyPort} used to manually copy a source file to the target
|
* the {@link GuiManualFileCopyPort} used to manually copy a source file to the target
|
||||||
* folder for documents that have not yet been successfully processed, and
|
* folder for documents that have not yet been successfully processed, and
|
||||||
* the {@link GuiHistoricalDocumentContextPort} used to retrieve the historical processing
|
* the {@link GuiHistoricalDocumentContextPort} used to retrieve the historical processing
|
||||||
* context for documents that were skipped in the current run, and the resolved application
|
* context for documents that were skipped in the current run, the resolved application
|
||||||
* version string that the status bar displays at the bottom of the main window.
|
* version string that the status bar displays at the bottom of the main window, and the
|
||||||
|
* optional {@link SchedulerControlUseCase} for controlling the automatic scheduler.
|
||||||
* <p>
|
* <p>
|
||||||
* The optional {@code applicationContextError} carries a human-readable German error
|
* The optional {@code applicationContextError} carries a human-readable German error
|
||||||
* message when the bootstrap-side application run context could not be initialised at
|
* message when the bootstrap-side application run context could not be initialised at
|
||||||
* startup (e.g., invalid or incomplete configuration). An empty value signals that the
|
* startup (e.g., invalid or incomplete configuration). An empty value signals that the
|
||||||
* run context was built successfully and batch runs can be launched immediately.
|
* run context was built successfully and batch runs can be launched immediately.
|
||||||
* <p>
|
* <p>
|
||||||
|
* The optional {@code schedulerControlUseCase} is present when the automatic scheduler
|
||||||
|
* was successfully wired at startup. An empty value means scheduler control is not
|
||||||
|
* available in this startup context (e.g., no valid configuration was loaded at startup).
|
||||||
|
* <p>
|
||||||
* All ports and services are supplied by Bootstrap so that the GUI adapter does not need to
|
* All ports and services are supplied by Bootstrap so that the GUI adapter does not need to
|
||||||
* know about provider-specific HTTP details or adapter wiring.
|
* know about provider-specific HTTP details or adapter wiring.
|
||||||
*/
|
*/
|
||||||
@@ -84,7 +90,8 @@ public record GuiStartupContext(
|
|||||||
GuiDeleteDocumentHistoryPort deleteDocumentHistoryPort,
|
GuiDeleteDocumentHistoryPort deleteDocumentHistoryPort,
|
||||||
GuiPromptEditorPortFactory promptEditorPortFactory,
|
GuiPromptEditorPortFactory promptEditorPortFactory,
|
||||||
GuiCreateNewDatabasePort createNewDatabasePort,
|
GuiCreateNewDatabasePort createNewDatabasePort,
|
||||||
Optional<String> applicationContextError) {
|
Optional<String> applicationContextError,
|
||||||
|
Optional<SchedulerControlUseCase> schedulerControlUseCase) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a fully wired startup context.
|
* Creates a fully wired startup context.
|
||||||
@@ -167,6 +174,75 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backward-compatible constructor that fills {@code schedulerControlUseCase} with
|
||||||
|
* {@link Optional#empty()}.
|
||||||
|
* <p>
|
||||||
|
* Preserves existing callers that were written before the scheduler was added.
|
||||||
|
*
|
||||||
|
* @param initialState initial editor state; must not be {@code null}
|
||||||
|
* @param startupNotice optional startup notice; {@code null} becomes empty
|
||||||
|
* @param configurationFileLoader file-loading callback; must not be {@code null}
|
||||||
|
* @param configurationFileWriter file-writing callback; must not be {@code null}
|
||||||
|
* @param modelCatalogPort port for retrieving AI model lists; must not be {@code null}
|
||||||
|
* @param apiKeyResolutionPort port for resolving API key provenance; must not be {@code null}
|
||||||
|
* @param providerTechnicalTestService service for provider-specific technical checks; must not be {@code null}
|
||||||
|
* @param pathCheckPort port for filesystem path accessibility checks; must not be {@code null}
|
||||||
|
* @param technicalTestOrchestrator orchestrator for the full technical test run; must not be {@code null}
|
||||||
|
* @param correctionExecutionService service for executing confirmed corrective actions; must not be {@code null}
|
||||||
|
* @param batchRunLauncher bridge that executes a regular batch run; must not be {@code null}
|
||||||
|
* @param miniRunLauncher bridge that executes a targeted mini-run; must not be {@code null}
|
||||||
|
* @param resetDocumentStatusPort bridge that resets document status; must not be {@code null}
|
||||||
|
* @param manualFileRenamePort bridge that renames a target file; must not be {@code null}
|
||||||
|
* @param manualFileCopyPort bridge that copies a source file; must not be {@code null}
|
||||||
|
* @param historicalDocumentContextPort bridge for historical processing context; must not be {@code null}
|
||||||
|
* @param applicationVersion resolved application version string; {@code null} defaults to {@code "dev"}
|
||||||
|
* @param promptEditorPort bridge zum Prompt-Editor-Use-Case; must not be {@code null}
|
||||||
|
* @param historyOverviewPort bridge for history overview; must not be {@code null}
|
||||||
|
* @param historyDetailsPort bridge for history details; must not be {@code null}
|
||||||
|
* @param historyResetDocumentStatusPort bridge for history reset; must not be {@code null}
|
||||||
|
* @param deleteDocumentHistoryPort bridge for history deletion; must not be {@code null}
|
||||||
|
* @param promptEditorPortFactory factory for prompt editor ports; must not be {@code null}
|
||||||
|
* @param createNewDatabasePort bridge for new database creation; must not be {@code null}
|
||||||
|
* @param applicationContextError optional error from context init; {@code null} becomes empty
|
||||||
|
*/
|
||||||
|
public GuiStartupContext(
|
||||||
|
GuiConfigurationEditorState initialState,
|
||||||
|
Optional<String> startupNotice,
|
||||||
|
GuiConfigurationFileLoader configurationFileLoader,
|
||||||
|
GuiConfigurationFileWriter configurationFileWriter,
|
||||||
|
AiModelCatalogPort modelCatalogPort,
|
||||||
|
ApiKeyResolutionPort apiKeyResolutionPort,
|
||||||
|
ProviderTechnicalTestService providerTechnicalTestService,
|
||||||
|
PathCheckPort pathCheckPort,
|
||||||
|
TechnicalTestOrchestrator technicalTestOrchestrator,
|
||||||
|
CorrectionExecutionService correctionExecutionService,
|
||||||
|
GuiBatchRunLauncher batchRunLauncher,
|
||||||
|
GuiMiniRunLauncher miniRunLauncher,
|
||||||
|
GuiResetDocumentStatusPort resetDocumentStatusPort,
|
||||||
|
GuiManualFileRenamePort manualFileRenamePort,
|
||||||
|
GuiManualFileCopyPort manualFileCopyPort,
|
||||||
|
GuiHistoricalDocumentContextPort historicalDocumentContextPort,
|
||||||
|
String applicationVersion,
|
||||||
|
GuiPromptEditorPort promptEditorPort,
|
||||||
|
GuiHistoryOverviewPort historyOverviewPort,
|
||||||
|
GuiHistoryDetailsPort historyDetailsPort,
|
||||||
|
GuiHistoryResetDocumentStatusPort historyResetDocumentStatusPort,
|
||||||
|
GuiDeleteDocumentHistoryPort deleteDocumentHistoryPort,
|
||||||
|
GuiPromptEditorPortFactory promptEditorPortFactory,
|
||||||
|
GuiCreateNewDatabasePort createNewDatabasePort,
|
||||||
|
Optional<String> applicationContextError) {
|
||||||
|
this(initialState, startupNotice, configurationFileLoader, configurationFileWriter,
|
||||||
|
modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort,
|
||||||
|
technicalTestOrchestrator, correctionExecutionService, batchRunLauncher,
|
||||||
|
miniRunLauncher, resetDocumentStatusPort, manualFileRenamePort, manualFileCopyPort,
|
||||||
|
historicalDocumentContextPort, applicationVersion, promptEditorPort,
|
||||||
|
historyOverviewPort, historyDetailsPort, historyResetDocumentStatusPort,
|
||||||
|
deleteDocumentHistoryPort, promptEditorPortFactory, createNewDatabasePort,
|
||||||
|
applicationContextError, Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+17
@@ -197,6 +197,23 @@ public class DefaultSchedulerControlUseCase implements SchedulerControlUseCase {
|
|||||||
return statusRef.get();
|
return statusRef.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Markiert den Autostart als fehlgeschlagen.
|
||||||
|
* <p>
|
||||||
|
* Wird von der Bootstrap-Schicht aufgerufen, wenn ein konfigurierter Autostart
|
||||||
|
* beim Programmstart fehlgeschlagen ist. Alle übrigen Statusfelder bleiben erhalten;
|
||||||
|
* lediglich {@link SchedulerStatus#autostartFailed()} wird auf {@code true} gesetzt.
|
||||||
|
* <p>
|
||||||
|
* Diese Methode darf nur aufgerufen werden, wenn der Scheduler noch gestoppt ist
|
||||||
|
* (unmittelbar nach einem fehlgeschlagenen {@link #start()}).
|
||||||
|
*/
|
||||||
|
public void markAutostartFailed() {
|
||||||
|
statusRef.updateAndGet(s -> new SchedulerStatus(
|
||||||
|
s.state(), s.lastRunEndedAt(), s.lastRunSummary(),
|
||||||
|
s.nextTickAt(), s.lastError(), true));
|
||||||
|
logger.warn("Scheduler-Status: Autostart als fehlgeschlagen markiert.");
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Tick-Wrapper (package-private für Testbarkeit)
|
// Tick-Wrapper (package-private für Testbarkeit)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -36,6 +36,11 @@
|
|||||||
<artifactId>pdf-umbenenner-adapter-out</artifactId>
|
<artifactId>pdf-umbenenner-adapter-out</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.gecheckt</groupId>
|
||||||
|
<artifactId>pdf-umbenenner-adapter-in-scheduler</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Logging -->
|
<!-- Logging -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
+121
-1
@@ -18,6 +18,8 @@ import org.apache.logging.log4j.LogManager;
|
|||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.cli.SchedulerBatchCommand;
|
import de.gecheckt.pdf.umbenenner.adapter.in.cli.SchedulerBatchCommand;
|
||||||
|
import de.gecheckt.pdf.umbenenner.adapter.in.scheduler.FileChannelConfigurationAccessAdapter;
|
||||||
|
import de.gecheckt.pdf.umbenenner.adapter.in.scheduler.ScheduledExecutorServiceSchedulerAdapter;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiAdapter;
|
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiAdapter;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiConfigurationFileLoader;
|
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiConfigurationFileLoader;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiConfigurationFileWriter;
|
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiConfigurationFileWriter;
|
||||||
@@ -61,6 +63,8 @@ import de.gecheckt.pdf.umbenenner.application.config.provider.ProviderConfigurat
|
|||||||
import de.gecheckt.pdf.umbenenner.application.config.startup.StartConfiguration;
|
import de.gecheckt.pdf.umbenenner.application.config.startup.StartConfiguration;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome;
|
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProcessingUseCase;
|
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProcessingUseCase;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStartException;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.CreateNewDatabaseUseCase;
|
import de.gecheckt.pdf.umbenenner.application.port.in.CreateNewDatabaseUseCase;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.HistoricalDocumentContext;
|
import de.gecheckt.pdf.umbenenner.application.port.in.HistoricalDocumentContext;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileCopyRequest;
|
import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileCopyRequest;
|
||||||
@@ -73,6 +77,9 @@ import de.gecheckt.pdf.umbenenner.application.port.in.ResetDocumentStatusResult;
|
|||||||
import de.gecheckt.pdf.umbenenner.application.port.in.ResolveHistoricalDocumentContextUseCase;
|
import de.gecheckt.pdf.umbenenner.application.port.in.ResolveHistoricalDocumentContextUseCase;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.ActiveDatabaseContextPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.ActiveDatabaseContextPort;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.AiContentSensitivity;
|
import de.gecheckt.pdf.umbenenner.application.port.out.AiContentSensitivity;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.BatchRunTrigger;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.BatchRunTriggerResult;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.RunSummary;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.AiInvocationPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.AiInvocationPort;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.ClockPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.ClockPort;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.DatabaseCreationPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.DatabaseCreationPort;
|
||||||
@@ -100,6 +107,7 @@ import de.gecheckt.pdf.umbenenner.adapter.in.gui.history.GuiHistoryResetDocument
|
|||||||
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteHistoryQueryAdapter;
|
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteHistoryQueryAdapter;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQueryPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQueryPort;
|
||||||
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultBatchRunProcessingUseCase;
|
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultBatchRunProcessingUseCase;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultSchedulerControlUseCase;
|
||||||
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultCreateNewDatabaseUseCase;
|
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultCreateNewDatabaseUseCase;
|
||||||
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultDeleteDocumentHistoryUseCase;
|
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultDeleteDocumentHistoryUseCase;
|
||||||
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultHistoryDetailsUseCase;
|
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultHistoryDetailsUseCase;
|
||||||
@@ -246,6 +254,17 @@ public class BootstrapRunner {
|
|||||||
*/
|
*/
|
||||||
private volatile Optional<ApplicationRunContext> guiApplicationRunContext = Optional.empty();
|
private volatile Optional<ApplicationRunContext> guiApplicationRunContext = Optional.empty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Scheduler-Use-Case, der beim GUI-Start mit einer gültigen Konfiguration
|
||||||
|
* initialisiert wird. Enthält den {@link DefaultSchedulerControlUseCase}, der den
|
||||||
|
* automatischen Scheduler-Lebenszyklus verwaltet.
|
||||||
|
* <p>
|
||||||
|
* Wird beim Start via {@link #tryInitializeScheduler(Path)} gesetzt. Leer, wenn
|
||||||
|
* beim Start keine gültige Konfiguration vorlag oder die Scheduler-Initialisierung
|
||||||
|
* fehlschlug. {@code volatile} sichert die Sichtbarkeit über Thread-Grenzen hinweg.
|
||||||
|
*/
|
||||||
|
private volatile Optional<DefaultSchedulerControlUseCase> guiSchedulerUseCase = Optional.empty();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functional interface encapsulating the legacy configuration migration step.
|
* Functional interface encapsulating the legacy configuration migration step.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -761,9 +780,28 @@ public class BootstrapRunner {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("GUI startup failed: {}", e.getMessage(), e);
|
LOG.error("GUI startup failed: {}", e.getMessage(), e);
|
||||||
return 1;
|
return 1;
|
||||||
|
} finally {
|
||||||
|
stopGuiSchedulerIfActive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stoppt den aktiven Scheduler, wenn die GUI beendet wird.
|
||||||
|
* <p>
|
||||||
|
* Wird im {@code finally}-Block von {@link #startGuiMode(Optional)} aufgerufen,
|
||||||
|
* damit der Scheduler-Thread ordentlich beendet wird, bevor der JVM-Prozess endet.
|
||||||
|
* Ist kein Scheduler aktiv oder bereits gestoppt, hat dieser Aufruf keine Wirkung.
|
||||||
|
*/
|
||||||
|
private void stopGuiSchedulerIfActive() {
|
||||||
|
guiSchedulerUseCase.ifPresent(scheduler -> {
|
||||||
|
if (scheduler.getStatus().state().isActive()) {
|
||||||
|
LOG.info("GUI-Beendigung: laufender Scheduler wird gestoppt.");
|
||||||
|
scheduler.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
guiSchedulerUseCase = Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the headless batch processing pipeline for the given startup arguments.
|
* Runs the headless batch processing pipeline for the given startup arguments.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -936,13 +974,19 @@ public class BootstrapRunner {
|
|||||||
GuiPromptEditorPort promptEditorPort = buildGuiPromptEditorPort(
|
GuiPromptEditorPort promptEditorPort = buildGuiPromptEditorPort(
|
||||||
loadedState.values().promptTemplateFile());
|
loadedState.values().promptTemplateFile());
|
||||||
Optional<String> contextError = initializeApplicationRunContext(configPath);
|
Optional<String> contextError = initializeApplicationRunContext(configPath);
|
||||||
|
if (contextError.isEmpty()) {
|
||||||
|
tryInitializeScheduler(configPath);
|
||||||
|
}
|
||||||
|
Optional<SchedulerControlUseCase> schedulerUseCase =
|
||||||
|
guiSchedulerUseCase.map(s -> (SchedulerControlUseCase) s);
|
||||||
return new GuiStartupContext(loadedState, Optional.empty(), loader, writer,
|
return new GuiStartupContext(loadedState, Optional.empty(), loader, writer,
|
||||||
modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort,
|
modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort,
|
||||||
technicalTestOrchestrator, correctionExecutionService, batchRunLauncher,
|
technicalTestOrchestrator, correctionExecutionService, batchRunLauncher,
|
||||||
miniRunLauncher, resetPort, manualRenamePort, manualCopyPort,
|
miniRunLauncher, resetPort, manualRenamePort, manualCopyPort,
|
||||||
historicalDocumentContextPort, applicationVersion, promptEditorPort,
|
historicalDocumentContextPort, applicationVersion, promptEditorPort,
|
||||||
historyOverviewPort, historyDetailsPort, historyResetPort, deleteHistoryPort,
|
historyOverviewPort, historyDetailsPort, historyResetPort, deleteHistoryPort,
|
||||||
this::buildGuiPromptEditorPort, createNewDatabasePort, contextError);
|
this::buildGuiPromptEditorPort, createNewDatabasePort, contextError,
|
||||||
|
schedulerUseCase);
|
||||||
} catch (GuiConfigurationLoadException e) {
|
} catch (GuiConfigurationLoadException e) {
|
||||||
LOG.error("GUI startup: configuration could not be loaded, starting without it: {}",
|
LOG.error("GUI startup: configuration could not be loaded, starting without it: {}",
|
||||||
e.getMessage(), e);
|
e.getMessage(), e);
|
||||||
@@ -975,6 +1019,82 @@ public class BootstrapRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialisiert den automatischen Scheduler für den GUI-Betrieb.
|
||||||
|
* <p>
|
||||||
|
* Erzeugt {@link FileChannelConfigurationAccessAdapter}, {@link ScheduledExecutorServiceSchedulerAdapter}
|
||||||
|
* und {@link DefaultSchedulerControlUseCase} und speichert den Use Case in
|
||||||
|
* {@link #guiSchedulerUseCase}. Ist in der Konfiguration {@code scheduler.enabled=true}
|
||||||
|
* gesetzt, wird der Scheduler sofort gestartet (Autostart).
|
||||||
|
* <p>
|
||||||
|
* Schlägt die Initialisierung fehl, wird {@link #guiSchedulerUseCase} auf
|
||||||
|
* {@link Optional#empty()} gesetzt und der Fehler als Warnung geloggt.
|
||||||
|
* Der GUI-Start wird dadurch nicht abgebrochen.
|
||||||
|
*
|
||||||
|
* @param configFilePath Pfad zur Konfigurationsdatei; muss auf Platte existieren
|
||||||
|
*/
|
||||||
|
private void tryInitializeScheduler(Path configFilePath) {
|
||||||
|
try {
|
||||||
|
FileChannelConfigurationAccessAdapter configAccessAdapter =
|
||||||
|
new FileChannelConfigurationAccessAdapter(configFilePath);
|
||||||
|
ScheduledExecutorServiceSchedulerAdapter schedulerAdapter =
|
||||||
|
new ScheduledExecutorServiceSchedulerAdapter(
|
||||||
|
result -> LOG.debug("Scheduler-Tick-Ergebnis: {}",
|
||||||
|
result.getClass().getSimpleName()));
|
||||||
|
BatchRunTrigger batchRunTrigger = () -> {
|
||||||
|
Optional<ApplicationRunContext> ctxOpt = guiApplicationRunContext;
|
||||||
|
if (ctxOpt.isEmpty()) {
|
||||||
|
return new BatchRunTriggerResult.Failed(
|
||||||
|
"Kein Anwendungskontext verfügbar.",
|
||||||
|
"guiApplicationRunContext ist leer – Scheduler-Tick übersprungen.");
|
||||||
|
}
|
||||||
|
ApplicationRunContext ctx = ctxOpt.get();
|
||||||
|
try {
|
||||||
|
RunLockPort runLockPort = runLockPortFactory.create(
|
||||||
|
resolveLockFilePath(ctx.startConfiguration()));
|
||||||
|
BatchRunContext runContext = createRunContext();
|
||||||
|
BatchRunProcessingUseCase useCase = buildProductionBatchUseCase(
|
||||||
|
ctx.startConfiguration(), runLockPort,
|
||||||
|
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver.noOp(),
|
||||||
|
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunCancellationToken.neverCancelled());
|
||||||
|
BatchRunOutcome outcome = useCase.execute(runContext);
|
||||||
|
runContext.setEndInstant(Instant.now());
|
||||||
|
if (outcome.isLockUnavailable()) {
|
||||||
|
return new BatchRunTriggerResult.SkippedBusy();
|
||||||
|
} else if (outcome.isSuccess()) {
|
||||||
|
return new BatchRunTriggerResult.Started(Instant.now(), RunSummary.noOp());
|
||||||
|
} else {
|
||||||
|
return new BatchRunTriggerResult.Failed(
|
||||||
|
"Verarbeitungslauf fehlgeschlagen.",
|
||||||
|
"BatchRunOutcome: " + outcome);
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
LOG.error("Scheduler-Tick: Unerwarteter Fehler: {}", e.getMessage(), e);
|
||||||
|
return new BatchRunTriggerResult.Failed(
|
||||||
|
"Unerwarteter Fehler: " + e.getMessage(),
|
||||||
|
e.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
DefaultSchedulerControlUseCase schedulerUseCase = new DefaultSchedulerControlUseCase(
|
||||||
|
schedulerAdapter, configAccessAdapter, configAccessAdapter, batchRunTrigger);
|
||||||
|
guiSchedulerUseCase = Optional.of(schedulerUseCase);
|
||||||
|
|
||||||
|
if (configAccessAdapter.loadSettings().enabled()) {
|
||||||
|
try {
|
||||||
|
schedulerUseCase.start();
|
||||||
|
LOG.info("Scheduler: Autostart aktiviert gemäß Konfiguration.");
|
||||||
|
} catch (SchedulerStartException e) {
|
||||||
|
LOG.warn("Scheduler: Autostart fehlgeschlagen: {}", e.getMessage());
|
||||||
|
schedulerUseCase.markAutostartFailed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Scheduler: Initialisierung fehlgeschlagen – Scheduler nicht verfügbar: {}",
|
||||||
|
e.getMessage(), e);
|
||||||
|
guiSchedulerUseCase = Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt einen vollständig verdrahteten {@link GuiPromptEditorPort} für den angegebenen
|
* Erzeugt einen vollständig verdrahteten {@link GuiPromptEditorPort} für den angegebenen
|
||||||
* Prompt-Dateipfad.
|
* Prompt-Dateipfad.
|
||||||
|
|||||||
Reference in New Issue
Block a user