From d5fdc9bcee50d57bccf2163c2b3b30b309a6611e Mon Sep 17 00:00:00 2001 From: naumueller Date: Fri, 6 Mar 2026 19:02:46 +0100 Subject: [PATCH] startup report added --- .../bootstrap/ApplicationInitializer.java | 52 +++++++++++++++++++ .../controller/DashboardController.java | 32 ++++++++---- src/main/java/vassistent/model/AppState.java | 45 +++++++++++----- 3 files changed, 105 insertions(+), 24 deletions(-) diff --git a/src/main/java/vassistent/bootstrap/ApplicationInitializer.java b/src/main/java/vassistent/bootstrap/ApplicationInitializer.java index b1d686e..dce5b97 100644 --- a/src/main/java/vassistent/bootstrap/ApplicationInitializer.java +++ b/src/main/java/vassistent/bootstrap/ApplicationInitializer.java @@ -5,6 +5,7 @@ import vassistent.model.AppState; import vassistent.service.*; import vassistent.util.AppConfigValidator; import vassistent.util.ConfigLoader; +import vassistent.util.Logger; import javax.swing.*; import java.util.Properties; @@ -99,9 +100,13 @@ public class ApplicationInitializer { context.setProcessManagerService(new ProcessManagerService(config)); + Logger.info("PROCESS", "Starting configured external processes"); + ProcessManagerService.StartupReport startupReport = context.getProcessManagerService().startProcesses(); + logProcessStartupReport(startupReport); + if (startupReport.hasFailures()) { context.getProcessManagerService().shutdown(); throw new IllegalStateException(startupReport.buildFailureMessage()); @@ -144,4 +149,51 @@ public class ApplicationInitializer { return trimmed; } + + /** + * Logs startup status for all process components and the global startup result. + * + * @param startupReport startup status report from {@link ProcessManagerService} + */ + private static void logProcessStartupReport( + ProcessManagerService.StartupReport startupReport + ) { + logProcessStartStatus(startupReport.getMqttSimulator()); + logProcessStartStatus(startupReport.getUnreal()); + + if (startupReport.hasFailures()) { + Logger.error("PROCESS", startupReport.buildFailureMessage()); + return; + } + + Logger.info("PROCESS", "External process startup completed successfully"); + } + + /** + * Logs one startup component status from the process startup report. + * + * @param status startup status for one managed process component + */ + private static void logProcessStartStatus( + ProcessManagerService.ProcessStartStatus status + ) { + String component = status.getComponentName(); + + if (!status.isEnabled()) { + Logger.info("PROCESS", component + " startup skipped (disabled)"); + return; + } + + if (status.isStarted()) { + Logger.info("PROCESS", component + " startup succeeded"); + return; + } + + String error = status.getErrorMessage(); + if (error == null || error.isBlank()) { + Logger.error("PROCESS", component + " startup failed"); + } else { + Logger.error("PROCESS", component + " startup failed: " + error); + } + } } diff --git a/src/main/java/vassistent/controller/DashboardController.java b/src/main/java/vassistent/controller/DashboardController.java index 19d0215..4f2d45f 100644 --- a/src/main/java/vassistent/controller/DashboardController.java +++ b/src/main/java/vassistent/controller/DashboardController.java @@ -8,6 +8,8 @@ import vassistent.util.Logger; import javax.swing.*; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** * Updates the dashboard chart and level bar whenever application state changes. @@ -16,6 +18,7 @@ public class DashboardController { private final StatisticsService statisticsService; private final DashboardView dashboardView; + private final ExecutorService dashboardUpdateExecutor; /** * Creates a dashboard controller and subscribes to app state changes. @@ -31,6 +34,12 @@ public class DashboardController { ) { this.statisticsService = statisticsService; this.dashboardView = dashboardView; + this.dashboardUpdateExecutor = + Executors.newSingleThreadExecutor(runnable -> { + Thread thread = new Thread(runnable, "dashboard-update"); + thread.setDaemon(true); + return thread; + }); appState.addListener(this::onStateChanged); } @@ -41,21 +50,22 @@ public class DashboardController { * @param state latest application state */ private void onStateChanged(AppState state) { + dashboardUpdateExecutor.execute(() -> { + List points = + statisticsService.getLastNAverages(120); - List points = - statisticsService.getLastNAverages(120); + SwingUtilities.invokeLater(() -> { - SwingUtilities.invokeLater(() -> { + Logger.info("STATISTICS", + "Entries loaded: " + points.size()); - Logger.info("STATISTICS", - "Entries loaded: " + points.size()); + dashboardView.updateChart(points); - dashboardView.updateChart(points); - - if (!points.isEmpty()) { - double ratio = points.get(points.size() - 1).getRatio(); - dashboardView.updateProblemLevel(ratio); - } + if (!points.isEmpty()) { + double ratio = points.get(points.size() - 1).getRatio(); + dashboardView.updateProblemLevel(ratio); + } + }); }); } } diff --git a/src/main/java/vassistent/model/AppState.java b/src/main/java/vassistent/model/AppState.java index a9fb56d..67fa7f6 100644 --- a/src/main/java/vassistent/model/AppState.java +++ b/src/main/java/vassistent/model/AppState.java @@ -1,7 +1,11 @@ package vassistent.model; -import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; /** @@ -9,12 +13,13 @@ import java.util.function.Consumer; */ public class AppState { - private long dataVersion = 0; - private boolean mqttConnected; - private ProblemLevel problemLevel = ProblemLevel.NONE; + private final AtomicLong dataVersion = new AtomicLong(0); + private final AtomicBoolean mqttConnected = new AtomicBoolean(false); + private final AtomicReference problemLevel = + new AtomicReference<>(ProblemLevel.NONE); private final List> listeners = - new ArrayList<>(); + new CopyOnWriteArrayList<>(); /** * Registers a state listener that is notified on relevant updates. @@ -22,14 +27,19 @@ public class AppState { * @param listener callback receiving the current state instance */ public void addListener(Consumer listener) { - listeners.add(listener); + listeners.add( + Objects.requireNonNull( + listener, + "listener must not be null" + ) + ); } /** * Notifies all currently registered listeners. */ private void notifyListeners() { - new ArrayList<>(listeners).forEach(l -> l.accept(this)); + listeners.forEach(listener -> listener.accept(this)); } /** @@ -38,7 +48,7 @@ public class AppState { * @return current problem level */ public ProblemLevel getProblemLevel() { - return problemLevel; + return problemLevel.get(); } /** @@ -47,8 +57,14 @@ public class AppState { * @param problemLevel new problem level */ public void setProblemLevel(ProblemLevel problemLevel) { - this.problemLevel = problemLevel; - dataVersion++; + this.problemLevel.set( + Objects.requireNonNull( + problemLevel, + "problemLevel must not be null" + ) + ); + + dataVersion.incrementAndGet(); notifyListeners(); } @@ -58,7 +74,7 @@ public class AppState { * @return {@code true} if connected, otherwise {@code false} */ public boolean isMqttConnected() { - return mqttConnected; + return mqttConnected.get(); } /** @@ -67,8 +83,11 @@ public class AppState { * @param mqttConnected new MQTT connection flag */ public void setMqttConnected(boolean mqttConnected) { - if (this.mqttConnected != mqttConnected) { - this.mqttConnected = mqttConnected; + boolean previous = + this.mqttConnected.getAndSet(mqttConnected); + + if (previous != mqttConnected) { + dataVersion.incrementAndGet(); notifyListeners(); } }