From a6b1b7eb016fb5eea06257fa97c5e65fe279713c Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 20 Aug 2025 08:33:55 +0200
Subject: [PATCH 01/23] First steps...
---
app/logbook/olog/ui/pom.xml | 10 ++++
.../ui/write/LogEntryEditorController.java | 50 +++++++++++++++++++
2 files changed, 60 insertions(+)
diff --git a/app/logbook/olog/ui/pom.xml b/app/logbook/olog/ui/pom.xml
index e320463bb0..f4511cb46e 100644
--- a/app/logbook/olog/ui/pom.xml
+++ b/app/logbook/olog/ui/pom.xml
@@ -39,6 +39,16 @@
app-logbook-olog-client-es5.0.3-SNAPSHOT
+
+ org.springframework
+ spring-websocket
+ 5.3.22
+
+
+ org.springframework
+ spring-messaging
+ 5.3.22
+ org.jfxtrasjfxtras-agenda
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java
index a4ae21527c..56e147ad30 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java
@@ -21,6 +21,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.SimpleType;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyBooleanProperty;
@@ -58,8 +59,10 @@
import javafx.scene.paint.Color;
+import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.concurrent.ExecutionException;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import org.phoebus.logbook.olog.ui.LogbookUIPreferences;
@@ -94,6 +97,15 @@
import org.phoebus.ui.dialog.ListSelectionPopOver;
import org.phoebus.ui.javafx.ImageCache;
import org.phoebus.util.time.TimestampFormats;
+import org.springframework.messaging.converter.StringMessageConverter;
+import org.springframework.messaging.simp.stomp.StompFrameHandler;
+import org.springframework.messaging.simp.stomp.StompHeaders;
+import org.springframework.messaging.simp.stomp.StompSession;
+import org.springframework.messaging.simp.stomp.StompSessionHandler;
+import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
+import org.springframework.web.socket.client.WebSocketClient;
+import org.springframework.web.socket.client.standard.StandardWebSocketClient;
+import org.springframework.web.socket.messaging.WebSocketStompClient;
import java.time.Instant;
import java.util.ArrayList;
@@ -544,6 +556,8 @@ public LogTemplate fromString(String name) {
setupTextAreaContextMenu();
+ doStompStuff();
+
}
/**
@@ -1066,4 +1080,40 @@ private void loadTemplate(LogTemplate logTemplate) {
selectedLogbooks.forEach(l -> updateDropDown(logbookDropDown, l, false));
}
}
+
+ private void doStompStuff(){
+ WebSocketClient webSocketClient = new StandardWebSocketClient();
+ WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
+ stompClient.setMessageConverter(new StringMessageConverter());
+ String url = "ws://localhost:8080/websocket";
+ StompSessionHandler sessionHandler = new MyStompSessionHandler();
+ try {
+ StompSession stompSession = stompClient.connect(url, sessionHandler).get();
+ stompSession.subscribe("/messages", new StompFrameHandler() {
+ @Override
+ public Type getPayloadType(StompHeaders headers) {
+ return String.class;
+ }
+
+ @Override
+ public void handleFrame(StompHeaders headers, Object payload) {
+ System.out.println(payload);
+ }
+ });
+ StompSession.Receiptable receiptable = stompSession.send("/websocket/echo", "Hello World");
+ receiptable.getReceiptId();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static class MyStompSessionHandler extends StompSessionHandlerAdapter {
+
+ @Override
+ public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
+ System.out.println();
+ }
+ }
}
From 2d5063791209dee4d572ffbc30c9e029d09e2a1e Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Fri, 22 Aug 2025 15:59:22 +0200
Subject: [PATCH 02/23] Initial step to Olog + web sockets
---
.../olog/ui/LogEntryTableViewController.java | 108 +++++++++++++++++-
.../olog/ui/LogbookSearchController.java | 11 +-
.../olog/ui/websocket/MessageType.java | 25 ++++
.../olog/ui/websocket/WebSocketMessage.java | 22 ++++
.../ui/write/LogEntryEditorController.java | 4 +-
5 files changed, 164 insertions(+), 6 deletions(-)
create mode 100644 app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/websocket/MessageType.java
create mode 100644 app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/websocket/WebSocketMessage.java
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
index f1fad641dd..11d7bbdc51 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
@@ -1,5 +1,7 @@
package org.phoebus.logbook.olog.ui;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
@@ -49,8 +51,12 @@
import org.phoebus.logbook.olog.ui.query.OlogQuery;
import org.phoebus.logbook.olog.ui.query.OlogQueryManager;
import org.phoebus.logbook.olog.ui.spi.Decoration;
+import org.phoebus.logbook.olog.ui.websocket.MessageType;
+import org.phoebus.logbook.olog.ui.websocket.WebSocketMessage;
import org.phoebus.logbook.olog.ui.write.EditMode;
+import org.phoebus.logbook.olog.ui.write.LogEntryEditorController;
import org.phoebus.logbook.olog.ui.write.LogEntryEditorStage;
+import org.phoebus.olog.es.api.Preferences;
import org.phoebus.olog.es.api.model.LogGroupProperty;
import org.phoebus.olog.es.api.model.OlogLog;
import org.phoebus.security.store.SecureStore;
@@ -58,12 +64,27 @@
import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.dialog.DialogHelper;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
+import org.springframework.lang.Nullable;
+import org.springframework.messaging.converter.StringMessageConverter;
+import org.springframework.messaging.simp.stomp.StompCommand;
+import org.springframework.messaging.simp.stomp.StompFrameHandler;
+import org.springframework.messaging.simp.stomp.StompHeaders;
+import org.springframework.messaging.simp.stomp.StompSession;
+import org.springframework.messaging.simp.stomp.StompSessionHandler;
+import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.web.socket.client.WebSocketClient;
+import org.springframework.web.socket.client.standard.StandardWebSocketClient;
+import org.springframework.web.socket.messaging.WebSocketStompClient;
import java.io.IOException;
+import java.lang.reflect.Type;
+import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -116,11 +137,14 @@ public class LogEntryTableViewController extends LogbookSearchController {
private Label openAdvancedSearchLabel;
// Model
private SearchResult searchResult;
+
+ private ObjectMapper objectMapper = new ObjectMapper();
+
/**
* List of selected log entries
*/
private final ObservableList selectedLogEntries = FXCollections.observableArrayList();
- private final Logger logger = Logger.getLogger(LogEntryTableViewController.class.getName());
+ private static final Logger logger = Logger.getLogger(LogEntryTableViewController.class.getName());
private final SimpleBooleanProperty showDetails = new SimpleBooleanProperty();
@@ -321,6 +345,8 @@ public void updateItem(TableViewListItem logEntry, boolean empty) {
Messages.AdvancedSearchHide : Messages.AdvancedSearchOpen,
advancedSearchVisibile));
+ connectWebSocket();
+
search();
}
@@ -388,8 +414,8 @@ public void search() {
searchResult1 -> {
searchInProgress.set(false);
setSearchResult(searchResult1);
- logger.log(Level.INFO, "Starting periodic search: " + queryString);
- periodicSearch(params, this::setSearchResult);
+ //logger.log(Level.INFO, "Starting periodic search: " + queryString);
+ //periodicSearch(params, this::setSearchResult);
List queries = ologQueryManager.getQueries();
Platform.runLater(() -> {
ologQueries.setAll(queries);
@@ -635,4 +661,80 @@ public boolean selectLogEntry(LogEntry logEntry) {
}
return false;
}
+
+ private void connectWebSocket(){
+ JobManager.schedule("Connect to web socket", monitor -> {
+ WebSocketClient webSocketClient = new StandardWebSocketClient();
+ WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
+ stompClient.setMessageConverter(new StringMessageConverter());
+ ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
+ threadPoolTaskScheduler.initialize();
+ stompClient.setTaskScheduler(threadPoolTaskScheduler);
+ stompClient.setDefaultHeartbeat(new long[]{10000, 10000});
+ String baseUrl = Preferences.olog_url;
+ URI uri = URI.create(baseUrl);
+ String scheme = uri.getScheme();
+ String host = uri.getHost();
+ int port = uri.getPort();
+ String path = uri.getPath();
+ if(path.endsWith("/")){
+ path = path.substring(0, path.length() - 1);
+ }
+ String webSocketScheme = scheme.toLowerCase().startsWith("https") ? "wss" : "ws";
+ String webSocketUrl = webSocketScheme + "://" + host + (port > -1 ? (":" + port) : "") + "/web-socket";
+ StompSessionHandler sessionHandler = new LogEntryTableViewController.MyStompSessionHandler();
+ try {
+ stompSession = stompClient.connect(webSocketUrl, sessionHandler).get();
+ stompSession.subscribe(path + "/web-socket/messages", new StompFrameHandler() {
+ @Override
+ public Type getPayloadType(StompHeaders headers) {
+ return String.class;
+ }
+
+ @Override
+ public void handleFrame(StompHeaders headers, Object payload) {
+ logger.log(Level.INFO, "Handling subscription frame: " + payload);
+ try {
+ WebSocketMessage webSocketMessage = objectMapper.readValue(payload.toString(), WebSocketMessage.class);
+ if(webSocketMessage.messageType().equals(MessageType.NEW_LOG_ENTRY)){
+ search();
+ }
+ } catch (JsonProcessingException e) {
+ logger.log(Level.WARNING, "Unable to deserialize payload " + payload.toString(), e);
+ }
+ }
+ });
+ //stompSession.send(path + "/web-socket/echo", "Hello World");
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Web socket connection attempt failed", e);
+ }
+ });
+ }
+
+ private static class MyStompSessionHandler extends StompSessionHandlerAdapter {
+
+
+ @Override
+ public void handleFrame(StompHeaders headers, @Nullable Object payload) {
+ if(payload != null){
+ logger.log(Level.INFO, "WebSocket frame received: " + payload);
+ }
+ }
+
+ @Override
+ public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
+ logger.log(Level.INFO, "Connected to web socket");
+ }
+
+ @Override
+ public void handleException(StompSession session, @Nullable StompCommand command,
+ StompHeaders headers, byte[] payload, Throwable exception) {
+ logger.log(Level.WARNING, "Exception encountered", exception);
+ }
+
+ @Override
+ public void handleTransportError(StompSession session, Throwable exception) {
+ logger.log(Level.WARNING, "Handling web socket transport error: " + exception.getMessage(), exception);
+ }
+ }
}
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
index ad62ca354c..bbdae65ed9 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
@@ -5,6 +5,7 @@
import org.phoebus.logbook.LogClient;
import org.phoebus.logbook.LogEntry;
import org.phoebus.logbook.SearchResult;
+import org.springframework.messaging.simp.stomp.StompSession;
import java.util.List;
import java.util.Map;
@@ -14,6 +15,8 @@
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* A basic controller for any ui performing logbook queries. The
@@ -32,6 +35,8 @@ public abstract class LogbookSearchController {
protected final SimpleBooleanProperty searchInProgress = new SimpleBooleanProperty(false);
private static final int SEARCH_JOB_INTERVAL = 30; // seconds
+ protected StompSession stompSession;
+
public void setClient(LogClient client) {
this.client = client;
}
@@ -91,6 +96,10 @@ private void cancelPeriodSearch() {
* Utility method to cancel any ongoing periodic search jobs.
*/
public void shutdown() {
- cancelPeriodSearch();
+ //cancelPeriodSearch();
+ if(stompSession != null && stompSession.isConnected()){
+ Logger.getLogger(LogbookSearchController.class.getName()).log(Level.INFO, "Disconnecting from web socket");
+ stompSession.disconnect();
+ }
}
}
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/websocket/MessageType.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/websocket/MessageType.java
new file mode 100644
index 0000000000..b7406c1ff1
--- /dev/null
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/websocket/MessageType.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package org.phoebus.logbook.olog.ui.websocket;
+
+public enum MessageType {
+ NEW_LOG_ENTRY,
+ LOG_ENTRY_UPDATED,
+ SHOW_BANNER
+}
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/websocket/WebSocketMessage.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/websocket/WebSocketMessage.java
new file mode 100644
index 0000000000..299304bcf8
--- /dev/null
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/websocket/WebSocketMessage.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package org.phoebus.logbook.olog.ui.websocket;
+
+public record WebSocketMessage(MessageType messageType, String payload) {
+}
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java
index 56e147ad30..9302de31b2 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java
@@ -556,7 +556,7 @@ public LogTemplate fromString(String name) {
setupTextAreaContextMenu();
- doStompStuff();
+ //doStompStuff();
}
@@ -1085,7 +1085,7 @@ private void doStompStuff(){
WebSocketClient webSocketClient = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new StringMessageConverter());
- String url = "ws://localhost:8080/websocket";
+ String url = "ws://localhost:8080/web-socket";
StompSessionHandler sessionHandler = new MyStompSessionHandler();
try {
StompSession stompSession = stompClient.connect(url, sessionHandler).get();
From a672fe2cbf98e76f1a270af1af2aa5481ebeaa6a Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Tue, 26 Aug 2025 10:58:58 +0200
Subject: [PATCH 03/23] Moved Olog web socket client to core-websocket
---
.../org/phoebus/core/websocket/WebSocketMessageHandler.java | 3 +++
.../core/websocket/springframework/WebSocketClientService.java | 3 +++
2 files changed, 6 insertions(+)
create mode 100644 core/websocket/src/main/java/org/phoebus/core/websocket/WebSocketMessageHandler.java
create mode 100644 core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/WebSocketMessageHandler.java b/core/websocket/src/main/java/org/phoebus/core/websocket/WebSocketMessageHandler.java
new file mode 100644
index 0000000000..96715d2494
--- /dev/null
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/WebSocketMessageHandler.java
@@ -0,0 +1,3 @@
+package org.phoebus.core.websocket;public interface WebSocketMessageHandler
+{
+}
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
new file mode 100644
index 0000000000..b6b141da25
--- /dev/null
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -0,0 +1,3 @@
+package org.phoebus.core.websocket.springframework;public class WebSocketClientService
+{
+}
From e2794d4578017669b58cc41800c501758a6b90e0 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Tue, 26 Aug 2025 11:01:38 +0200
Subject: [PATCH 04/23] Olog web socket: use core-websocket
---
app/logbook/olog/ui/pom.xml | 15 +-
.../olog/ui/LogEntryTableViewController.java | 151 ++++++---------
.../org/phoebus/logbook/olog/ui/Messages.java | 2 +
.../logbook/olog/ui/LogEntryTableView.fxml | 112 +++++------
.../logbook/olog/ui/messages.properties | 4 +-
core/websocket/pom.xml | 20 ++
.../websocket/WebSocketMessageHandler.java | 11 +-
.../WebSocketClientService.java | 175 +++++++++++++++++-
8 files changed, 318 insertions(+), 172 deletions(-)
diff --git a/app/logbook/olog/ui/pom.xml b/app/logbook/olog/ui/pom.xml
index f4511cb46e..9b26eb88b8 100644
--- a/app/logbook/olog/ui/pom.xml
+++ b/app/logbook/olog/ui/pom.xml
@@ -14,6 +14,11 @@
core-framework5.0.3-SNAPSHOT
+
+ org.phoebus
+ core-websocket
+ 5.0.3-SNAPSHOT
+ org.phoebuscore-ui
@@ -39,16 +44,6 @@
app-logbook-olog-client-es5.0.3-SNAPSHOT
-
- org.springframework
- spring-websocket
- 5.3.22
-
-
- org.springframework
- spring-messaging
- 5.3.22
- org.jfxtrasjfxtras-agenda
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
index 11d7bbdc51..a0fdf388b0 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
@@ -41,6 +41,8 @@
import javafx.util.Callback;
import javafx.util.Duration;
import javafx.util.StringConverter;
+import org.phoebus.core.websocket.WebSocketMessageHandler;
+import org.phoebus.core.websocket.springframework.WebSocketClientService;
import org.phoebus.framework.jobs.JobManager;
import org.phoebus.logbook.LogClient;
import org.phoebus.logbook.LogEntry;
@@ -54,7 +56,6 @@
import org.phoebus.logbook.olog.ui.websocket.MessageType;
import org.phoebus.logbook.olog.ui.websocket.WebSocketMessage;
import org.phoebus.logbook.olog.ui.write.EditMode;
-import org.phoebus.logbook.olog.ui.write.LogEntryEditorController;
import org.phoebus.logbook.olog.ui.write.LogEntryEditorStage;
import org.phoebus.olog.es.api.Preferences;
import org.phoebus.olog.es.api.model.LogGroupProperty;
@@ -64,27 +65,13 @@
import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.dialog.DialogHelper;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
-import org.springframework.lang.Nullable;
-import org.springframework.messaging.converter.StringMessageConverter;
-import org.springframework.messaging.simp.stomp.StompCommand;
-import org.springframework.messaging.simp.stomp.StompFrameHandler;
-import org.springframework.messaging.simp.stomp.StompHeaders;
-import org.springframework.messaging.simp.stomp.StompSession;
-import org.springframework.messaging.simp.stomp.StompSessionHandler;
-import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
-import org.springframework.web.socket.client.WebSocketClient;
-import org.springframework.web.socket.client.standard.StandardWebSocketClient;
-import org.springframework.web.socket.messaging.WebSocketStompClient;
import java.io.IOException;
-import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -96,7 +83,7 @@
*
* @author Kunal Shroff
*/
-public class LogEntryTableViewController extends LogbookSearchController {
+public class LogEntryTableViewController extends LogbookSearchController implements WebSocketMessageHandler {
@FXML
@SuppressWarnings("unused")
@@ -140,6 +127,11 @@ public class LogEntryTableViewController extends LogbookSearchController {
private ObjectMapper objectMapper = new ObjectMapper();
+ @FXML
+ private Label autoUpdateStatusLabel;
+
+ private SimpleBooleanProperty webSocketConnected = new SimpleBooleanProperty();
+
/**
* List of selected log entries
*/
@@ -148,7 +140,9 @@ public class LogEntryTableViewController extends LogbookSearchController {
private final SimpleBooleanProperty showDetails = new SimpleBooleanProperty();
- private final SimpleBooleanProperty advancedSearchVisibile = new SimpleBooleanProperty(false);
+ private final SimpleBooleanProperty advancedSearchVisible = new SimpleBooleanProperty(false);
+
+ private WebSocketClientService webSocketClientService;
/**
* Constructor.
@@ -341,9 +335,20 @@ public void updateItem(TableViewListItem logEntry, boolean empty) {
openAdvancedSearchLabel.setOnMouseClicked(e -> resize());
openAdvancedSearchLabel.textProperty()
- .bind(Bindings.createStringBinding(() -> advancedSearchVisibile.get() ?
+ .bind(Bindings.createStringBinding(() -> advancedSearchVisible.get() ?
Messages.AdvancedSearchHide : Messages.AdvancedSearchOpen,
- advancedSearchVisibile));
+ advancedSearchVisible));
+
+ webSocketConnected.addListener((obs, o, n) -> {
+ if(n){
+ autoUpdateStatusLabel.setStyle("-fx-text-fill: black;");
+ autoUpdateStatusLabel.setText(Messages.AutoRefreshOn);
+ }
+ else{
+ autoUpdateStatusLabel.setStyle("-fx-text-fill: red;");
+ autoUpdateStatusLabel.setText(Messages.AutoRefreshOff);
+ }
+ });
connectWebSocket();
@@ -359,13 +364,13 @@ public void resize() {
if (!moving.compareAndExchangeAcquire(false, true)) {
Duration cycleDuration = Duration.millis(400);
Timeline timeline;
- if (advancedSearchVisibile.get()) {
+ if (advancedSearchVisible.get()) {
query.disableProperty().set(false);
KeyValue kv = new KeyValue(advancedSearchViewController.getPane().minWidthProperty(), 0);
KeyValue kv2 = new KeyValue(advancedSearchViewController.getPane().maxWidthProperty(), 0);
timeline = new Timeline(new KeyFrame(cycleDuration, kv, kv2));
timeline.setOnFinished(event -> {
- advancedSearchVisibile.set(false);
+ advancedSearchVisible.set(false);
moving.set(false);
search();
});
@@ -376,7 +381,7 @@ public void resize() {
KeyValue kv2 = new KeyValue(advancedSearchViewController.getPane().prefWidthProperty(), width);
timeline = new Timeline(new KeyFrame(cycleDuration, kv, kv2));
timeline.setOnFinished(event -> {
- advancedSearchVisibile.set(true);
+ advancedSearchVisible.set(true);
moving.set(false);
query.disableProperty().set(true);
});
@@ -522,7 +527,7 @@ private void createLogEntryGroup() {
}
});
}
-
+q
@FXML
@SuppressWarnings("unused")
public void goToFirstPage() {
@@ -635,6 +640,10 @@ public void logEntryChanged(LogEntry logEntry) {
setLogEntry(logEntry);
}
+ private void logEntryChanged(String logEntryId){
+ search();
+ }
+
protected LogEntry getLogEntry() {
return logEntryDisplayController.getLogEntry();
}
@@ -663,78 +672,38 @@ public boolean selectLogEntry(LogEntry logEntry) {
}
private void connectWebSocket(){
- JobManager.schedule("Connect to web socket", monitor -> {
- WebSocketClient webSocketClient = new StandardWebSocketClient();
- WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
- stompClient.setMessageConverter(new StringMessageConverter());
- ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
- threadPoolTaskScheduler.initialize();
- stompClient.setTaskScheduler(threadPoolTaskScheduler);
- stompClient.setDefaultHeartbeat(new long[]{10000, 10000});
- String baseUrl = Preferences.olog_url;
- URI uri = URI.create(baseUrl);
- String scheme = uri.getScheme();
- String host = uri.getHost();
- int port = uri.getPort();
- String path = uri.getPath();
- if(path.endsWith("/")){
- path = path.substring(0, path.length() - 1);
- }
- String webSocketScheme = scheme.toLowerCase().startsWith("https") ? "wss" : "ws";
- String webSocketUrl = webSocketScheme + "://" + host + (port > -1 ? (":" + port) : "") + "/web-socket";
- StompSessionHandler sessionHandler = new LogEntryTableViewController.MyStompSessionHandler();
- try {
- stompSession = stompClient.connect(webSocketUrl, sessionHandler).get();
- stompSession.subscribe(path + "/web-socket/messages", new StompFrameHandler() {
- @Override
- public Type getPayloadType(StompHeaders headers) {
- return String.class;
- }
-
- @Override
- public void handleFrame(StompHeaders headers, Object payload) {
- logger.log(Level.INFO, "Handling subscription frame: " + payload);
- try {
- WebSocketMessage webSocketMessage = objectMapper.readValue(payload.toString(), WebSocketMessage.class);
- if(webSocketMessage.messageType().equals(MessageType.NEW_LOG_ENTRY)){
- search();
- }
- } catch (JsonProcessingException e) {
- logger.log(Level.WARNING, "Unable to deserialize payload " + payload.toString(), e);
- }
- }
- });
- //stompSession.send(path + "/web-socket/echo", "Hello World");
- } catch (Exception e) {
- logger.log(Level.WARNING, "Web socket connection attempt failed", e);
- }
+ String baseUrl = Preferences.olog_url;
+ URI uri = URI.create(baseUrl);
+ String scheme = uri.getScheme();
+ String host = uri.getHost();
+ int port = uri.getPort();
+ String path = uri.getPath();
+ if(path.endsWith("/")){
+ path = path.substring(0, path.length() - 1);
+ }
+ String webSocketScheme = scheme.toLowerCase().startsWith("https") ? "wss" : "ws";
+ String webSocketUrl = webSocketScheme + "://" + host + (port > -1 ? (":" + port) : "") + path;
+
+ webSocketClientService = new WebSocketClientService(() -> {
+ logger.log(Level.INFO, "Connected to web socket on " + webSocketUrl);
+ webSocketConnected.set(true);
+ }, () -> {
+ logger.log(Level.INFO, "Disconnected from web socket on " + webSocketUrl);
+ webSocketConnected.set(false);
});
+ webSocketClientService.addWebSocketMessageHandler(this);
+ webSocketClientService.connect(webSocketUrl);
}
- private static class MyStompSessionHandler extends StompSessionHandlerAdapter {
-
-
- @Override
- public void handleFrame(StompHeaders headers, @Nullable Object payload) {
- if(payload != null){
- logger.log(Level.INFO, "WebSocket frame received: " + payload);
+ @Override
+ public void handleWebSocketMessage(String message){
+ try {
+ WebSocketMessage webSocketMessage = objectMapper.readValue(message, WebSocketMessage.class);
+ if(webSocketMessage.messageType().equals(MessageType.NEW_LOG_ENTRY)){
+ search();
}
- }
-
- @Override
- public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
- logger.log(Level.INFO, "Connected to web socket");
- }
-
- @Override
- public void handleException(StompSession session, @Nullable StompCommand command,
- StompHeaders headers, byte[] payload, Throwable exception) {
- logger.log(Level.WARNING, "Exception encountered", exception);
- }
-
- @Override
- public void handleTransportError(StompSession session, Throwable exception) {
- logger.log(Level.WARNING, "Handling web socket transport error: " + exception.getMessage(), exception);
+ } catch (JsonProcessingException e) {
+ logger.log(Level.WARNING, "Unable to deserialize message \"" + message + "\"");
}
}
}
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/Messages.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/Messages.java
index 60d2bd92cc..b92638a38b 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/Messages.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/Messages.java
@@ -23,6 +23,8 @@ public class Messages
AttachmentsDirectoryNotWritable,
AttachmentsFileNotDirectory,
AttachmentsNoStorage,
+ AutoRefreshOn,
+ AutoRefreshOff,
AvailableTemplates,
Back,
CloseRequestHeader,
diff --git a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryTableView.fxml b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryTableView.fxml
index 55dbbcbf09..35c827d0d4 100644
--- a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryTableView.fxml
+++ b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryTableView.fxml
@@ -1,113 +1,94 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
+
-
-
+
+
-
-
-
+
+
+
-
+
-
+
-
+
-
-
+
-
+
diff --git a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/messages.properties b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/messages.properties
index d07f1c680a..7573638fcc 100644
--- a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/messages.properties
+++ b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/messages.properties
@@ -11,11 +11,13 @@ AttachmentsDirectoryNotWritable=Attachments directory {0} is not writable
ArchivedSaveFailed=Failed to save archived log entries to file
AttachmentsNoStorage=No storage for attachments
ArchivedLaunchExternalAppFailed=Unable to launch external application to view file "{0}". Please check your settings.
-Author=Author:
Attachments=Attachments
AttachmentsDirectoryFailedCreate=Failed to create directory {0} for attachments
AttachmentsFileNotDirectory=File {0} exists but is not a directory
AttachmentsSearchProperty=Attachments:
+Author=Author:
+AutoRefreshOn=Auto Refresh: ON
+AutoRefreshOff=Auto Refresh: OFF
AvailableTemplates=Available Templates
Back=Back
BrowseButton=Browse
diff --git a/core/websocket/pom.xml b/core/websocket/pom.xml
index 6bbe829423..cc29c6313b 100644
--- a/core/websocket/pom.xml
+++ b/core/websocket/pom.xml
@@ -12,7 +12,27 @@
5.0.3-SNAPSHOT
+
+ 5.3.22
+
+
+
+ org.phoebus
+ core-framework
+ 5.0.3-SNAPSHOT
+
+
+ org.springframework
+ spring-websocket
+ ${spring.framework.version}
+
+
+ org.springframework
+ spring-messaging
+ ${spring.framework.version}
+
+
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/WebSocketMessageHandler.java b/core/websocket/src/main/java/org/phoebus/core/websocket/WebSocketMessageHandler.java
index 96715d2494..7299c2a2fd 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/WebSocketMessageHandler.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/WebSocketMessageHandler.java
@@ -1,3 +1,10 @@
-package org.phoebus.core.websocket;public interface WebSocketMessageHandler
-{
+/*
+ * Copyright (C) 2025 European Spallation Source ERIC.
+ */
+
+package org.phoebus.core.websocket;
+
+public interface WebSocketMessageHandler {
+
+ void handleWebSocketMessage(String message);
}
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
index b6b141da25..82ffd68b0c 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -1,3 +1,174 @@
-package org.phoebus.core.websocket.springframework;public class WebSocketClientService
-{
+/*
+ * Copyright (C) 2025 European Spallation Source ERIC.
+ */
+
+package org.phoebus.core.websocket.springframework;
+
+import org.phoebus.core.websocket.WebSocketMessageHandler;
+import org.phoebus.framework.jobs.JobManager;
+import org.springframework.lang.Nullable;
+import org.springframework.messaging.converter.StringMessageConverter;
+import org.springframework.messaging.simp.stomp.StompCommand;
+import org.springframework.messaging.simp.stomp.StompFrameHandler;
+import org.springframework.messaging.simp.stomp.StompHeaders;
+import org.springframework.messaging.simp.stomp.StompSession;
+import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.web.socket.client.WebSocketClient;
+import org.springframework.web.socket.client.standard.StandardWebSocketClient;
+import org.springframework.web.socket.messaging.WebSocketStompClient;
+
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Service wrapping a STOMP web socket client based on Spring Framework APIs.
+ * Features:
+ *
+ *
Manages keep alive as supported by the Spring Framework libs
+ *
Passes string messages to registered {@link WebSocketMessageHandler}a
+ *
Calls {@link Runnable}s (if specified) to signal connection or disconnection
+ *
Attempts to auto-reconnect if web socket is closed by remote peer.
+ *
+ *
+ * All messages received from the remote peer are strings only, but may be JSON formatted.
+ * In other words, depending on the use case a client may wish to deserialize messages.
+ *
+ * Since web socket URL paths are currently hard coded, a remote peer (e.g. Spring Framework STOMP web socket service) must:
+ *
+ *
Publish a connect URL like ws(s)://host:port/path/web-socket, where path is optional.
+ *
Publish a topic named /path/web-socket/messages, where path is optional.
+ *
+ *
+ */
+public class WebSocketClientService {
+
+ private String baseUrl;
+ private StompSession stompSession;
+ private Runnable connectCallback;
+ private Runnable disconnectCallback;
+ private final List webSocketMessageHandlers = Collections.synchronizedList(new ArrayList<>());
+
+ private static final Logger logger = Logger.getLogger(WebSocketClientService.class.getName());
+
+ public WebSocketClientService(){
+
+ }
+
+ /**
+ * @param connectCallback The non-null method called when connection to the remote web socket has been successfully established.
+ * @param disconnectCallback The non-null method called when connection to the remote web socket has been lost, e.g.
+ * remote peer has been shut down.
+ */
+ public WebSocketClientService(Runnable connectCallback, Runnable disconnectCallback) {
+ this.connectCallback = connectCallback;
+ this.disconnectCallback = disconnectCallback;
+ }
+
+ public void setConnectCallback(Runnable connectCallback){
+ this.connectCallback = connectCallback;
+ }
+
+ public void setDisconnectCallback(Runnable disconnectCallback){
+ this.disconnectCallback = disconnectCallback;
+ }
+
+ public void addWebSocketMessageHandler(WebSocketMessageHandler webSocketMessageHandler) {
+ webSocketMessageHandlers.add(webSocketMessageHandler);
+ }
+
+ public void removeWebSocketMessageHandler(WebSocketMessageHandler webSocketMessageHandler) {
+ webSocketMessageHandlers.remove(webSocketMessageHandler);
+ }
+
+ private void connect(){
+ connect(baseUrl);
+ }
+
+ /**
+ * Attempts to connect to remote web socket.
+ * @param baseUrl The "base" URL of the web socket peer, must start with ws:// or wss://. Note that "web-socket" will be
+ * appended to this URL. Further, the URL may contain a path, e.g. ws://host:port/path.
+ * @throws IllegalArgumentException if baseUrl is null, empty or does not start with ws:// or wss://.
+ */
+ public void connect(String baseUrl) {
+ if(baseUrl == null || baseUrl.isEmpty() || (!baseUrl.toLowerCase().startsWith("ws://") && !baseUrl.toLowerCase().startsWith("wss://"))){
+ throw new IllegalArgumentException("URL \"" + baseUrl + "\" is not valid");
+ }
+ this.baseUrl = baseUrl;
+ URI uri = URI.create(baseUrl);
+ String scheme = uri.getScheme();
+ String host = uri.getHost();
+ int port = uri.getPort();
+ String path = uri.getPath();
+ if (path.endsWith("/")) {
+ path = path.substring(0, path.length() - 1);
+ }
+ WebSocketClient webSocketClient = new StandardWebSocketClient();
+ WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
+ stompClient.setMessageConverter(new StringMessageConverter());
+ ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
+ threadPoolTaskScheduler.initialize();
+ stompClient.setTaskScheduler(threadPoolTaskScheduler);
+ stompClient.setDefaultHeartbeat(new long[]{30000, 30000});
+ StompSessionHandler sessionHandler = new StompSessionHandler();
+ String _path = path;
+ String webSocketUrl = scheme + "://" + host + (port > -1 ? (":" + port) : "") + "/web-socket";
+ JobManager.schedule("Connect to web socket", monitor -> {
+ stompSession = stompClient.connect(webSocketUrl, sessionHandler).get();
+ logger.log(Level.INFO, "Subscribing to messages on " + _path + "/web-socket/messages");
+ stompSession.subscribe(_path + "/web-socket/messages", new StompFrameHandler() {
+ @Override
+ public Type getPayloadType(StompHeaders headers) {
+ return String.class;
+ }
+
+ @Override
+ public void handleFrame(StompHeaders headers, Object payload) {
+ logger.log(Level.INFO, "Handling subscription frame: " + payload);
+ webSocketMessageHandlers.forEach(h -> h.handleWebSocketMessage((String) payload));
+ }
+ });
+ });
+ }
+
+ /**
+ * Handler used to perform housekeeping...
+ */
+ private class StompSessionHandler extends StompSessionHandlerAdapter {
+
+ @Override
+ public void handleFrame(StompHeaders headers, @Nullable Object payload) {
+ if (payload != null) {
+ logger.log(Level.INFO, "WebSocket frame received: " + payload);
+ }
+ }
+
+ @Override
+ public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
+ logger.log(Level.INFO, "Connected to web socket");
+ if (connectCallback != null) {
+ connectCallback.run();
+ }
+ }
+
+ @Override
+ public void handleException(StompSession session, @Nullable StompCommand command,
+ StompHeaders headers, byte[] payload, Throwable exception) {
+ logger.log(Level.WARNING, "Exception encountered", exception);
+ }
+
+ @Override
+ public void handleTransportError(StompSession session, Throwable exception) {
+ logger.log(Level.WARNING, "Handling web socket transport error: " + exception.getMessage(), exception);
+ if (disconnectCallback != null) {
+ disconnectCallback.run();
+ }
+ }
+ }
}
From 79b2f7be007d22c28b79e2d4a159149dd80d61fb Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Tue, 26 Aug 2025 16:07:51 +0200
Subject: [PATCH 05/23] Make sure web socket URL considers path (context) if
specified
---
.../phoebus/logbook/olog/ui/LogEntryTableViewController.java | 2 +-
.../core/websocket/springframework/WebSocketClientService.java | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
index a0fdf388b0..5b6de03442 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
@@ -527,7 +527,7 @@ private void createLogEntryGroup() {
}
});
}
-q
+
@FXML
@SuppressWarnings("unused")
public void goToFirstPage() {
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
index 82ffd68b0c..742f37821d 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -118,7 +118,7 @@ public void connect(String baseUrl) {
stompClient.setDefaultHeartbeat(new long[]{30000, 30000});
StompSessionHandler sessionHandler = new StompSessionHandler();
String _path = path;
- String webSocketUrl = scheme + "://" + host + (port > -1 ? (":" + port) : "") + "/web-socket";
+ String webSocketUrl = scheme + "://" + host + (port > -1 ? (":" + port) : "") + _path + "/web-socket";
JobManager.schedule("Connect to web socket", monitor -> {
stompSession = stompClient.connect(webSocketUrl, sessionHandler).get();
logger.log(Level.INFO, "Subscribing to messages on " + _path + "/web-socket/messages");
From e6ef4f24f9b11ea0c6332e8585349f938cc7731e Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 27 Aug 2025 12:42:53 +0200
Subject: [PATCH 06/23] Adding STOMP session reconnect logic
---
.../olog/ui/LogEntryTableViewController.java | 6 +-
.../WebSocketClientService.java | 101 +++++++++++++-----
2 files changed, 77 insertions(+), 30 deletions(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
index 5b6de03442..7fe7b1f3cc 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
@@ -686,10 +686,10 @@ private void connectWebSocket(){
webSocketClientService = new WebSocketClientService(() -> {
logger.log(Level.INFO, "Connected to web socket on " + webSocketUrl);
- webSocketConnected.set(true);
+ Platform.runLater(() -> webSocketConnected.set(true));
}, () -> {
logger.log(Level.INFO, "Disconnected from web socket on " + webSocketUrl);
- webSocketConnected.set(false);
+ Platform.runLater(() -> webSocketConnected.set(false));
});
webSocketClientService.addWebSocketMessageHandler(this);
webSocketClientService.connect(webSocketUrl);
@@ -701,6 +701,8 @@ public void handleWebSocketMessage(String message){
WebSocketMessage webSocketMessage = objectMapper.readValue(message, WebSocketMessage.class);
if(webSocketMessage.messageType().equals(MessageType.NEW_LOG_ENTRY)){
search();
+ webSocketClientService.close();
+ webSocketClientService.sendEcho("Hello World");
}
} catch (JsonProcessingException e) {
logger.log(Level.WARNING, "Unable to deserialize message \"" + message + "\"");
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
index 742f37821d..d6684ed75d 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -5,9 +5,9 @@
package org.phoebus.core.websocket.springframework;
import org.phoebus.core.websocket.WebSocketMessageHandler;
-import org.phoebus.framework.jobs.JobManager;
import org.springframework.lang.Nullable;
import org.springframework.messaging.converter.StringMessageConverter;
+import org.springframework.messaging.simp.stomp.ConnectionLostException;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompFrameHandler;
import org.springframework.messaging.simp.stomp.StompHeaders;
@@ -23,6 +23,8 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -48,20 +50,28 @@
*/
public class WebSocketClientService {
- private String baseUrl;
+ /**
+ * URL to which the client connects
+ */
+ private String webSocketConnectUrl;
+ /**
+ * Path string, depends on service deployment context, e.g. Olog
+ */
+ private String contextPath;
private StompSession stompSession;
private Runnable connectCallback;
private Runnable disconnectCallback;
private final List webSocketMessageHandlers = Collections.synchronizedList(new ArrayList<>());
+ private final AtomicBoolean attemptReconnect = new AtomicBoolean();
private static final Logger logger = Logger.getLogger(WebSocketClientService.class.getName());
- public WebSocketClientService(){
+ public WebSocketClientService() {
}
/**
- * @param connectCallback The non-null method called when connection to the remote web socket has been successfully established.
+ * @param connectCallback The non-null method called when connection to the remote web socket has been successfully established.
* @param disconnectCallback The non-null method called when connection to the remote web socket has been lost, e.g.
* remote peer has been shut down.
*/
@@ -70,11 +80,11 @@ public WebSocketClientService(Runnable connectCallback, Runnable disconnectCallb
this.disconnectCallback = disconnectCallback;
}
- public void setConnectCallback(Runnable connectCallback){
+ public void setConnectCallback(Runnable connectCallback) {
this.connectCallback = connectCallback;
}
- public void setDisconnectCallback(Runnable disconnectCallback){
+ public void setDisconnectCallback(Runnable disconnectCallback) {
this.disconnectCallback = disconnectCallback;
}
@@ -86,21 +96,26 @@ public void removeWebSocketMessageHandler(WebSocketMessageHandler webSocketMessa
webSocketMessageHandlers.remove(webSocketMessageHandler);
}
- private void connect(){
- connect(baseUrl);
+
+ public void sendEcho(String message) {
+ stompSession.send(contextPath + "/web-socket/echo", message);
+ }
+
+ public void close(){
+ stompSession.disconnect();
}
/**
* Attempts to connect to remote web socket.
+ *
* @param baseUrl The "base" URL of the web socket peer, must start with ws:// or wss://. Note that "web-socket" will be
* appended to this URL. Further, the URL may contain a path, e.g. ws://host:port/path.
* @throws IllegalArgumentException if baseUrl is null, empty or does not start with ws:// or wss://.
*/
public void connect(String baseUrl) {
- if(baseUrl == null || baseUrl.isEmpty() || (!baseUrl.toLowerCase().startsWith("ws://") && !baseUrl.toLowerCase().startsWith("wss://"))){
+ if (baseUrl == null || baseUrl.isEmpty() || (!baseUrl.toLowerCase().startsWith("ws://") && !baseUrl.toLowerCase().startsWith("wss://"))) {
throw new IllegalArgumentException("URL \"" + baseUrl + "\" is not valid");
}
- this.baseUrl = baseUrl;
URI uri = URI.create(baseUrl);
String scheme = uri.getScheme();
String host = uri.getHost();
@@ -109,6 +124,13 @@ public void connect(String baseUrl) {
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
+ this.contextPath = path;
+ this.webSocketConnectUrl = scheme + "://" + host + (port > -1 ? (":" + port) : "") + this.contextPath + "/web-socket";
+ doConnect();
+ }
+
+ private void doConnect() {
+ attemptReconnect.set(true);
WebSocketClient webSocketClient = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new StringMessageConverter());
@@ -117,28 +139,37 @@ public void connect(String baseUrl) {
stompClient.setTaskScheduler(threadPoolTaskScheduler);
stompClient.setDefaultHeartbeat(new long[]{30000, 30000});
StompSessionHandler sessionHandler = new StompSessionHandler();
- String _path = path;
- String webSocketUrl = scheme + "://" + host + (port > -1 ? (":" + port) : "") + _path + "/web-socket";
- JobManager.schedule("Connect to web socket", monitor -> {
- stompSession = stompClient.connect(webSocketUrl, sessionHandler).get();
- logger.log(Level.INFO, "Subscribing to messages on " + _path + "/web-socket/messages");
- stompSession.subscribe(_path + "/web-socket/messages", new StompFrameHandler() {
- @Override
- public Type getPayloadType(StompHeaders headers) {
- return String.class;
+ new Thread(() -> {
+ while (attemptReconnect.get()) {
+ logger.log(Level.INFO, "Attempting web socket connection to " + webSocketConnectUrl);
+ try {
+ stompSession = stompClient.connect(this.webSocketConnectUrl, sessionHandler).get();
+ stompSession.subscribe(contextPath + "/web-socket/messages", new StompFrameHandler() {
+ @Override
+ public Type getPayloadType(StompHeaders headers) {
+ return String.class;
+ }
+
+ @Override
+ public void handleFrame(StompHeaders headers, Object payload) {
+ logger.log(Level.INFO, "Handling subscription frame: " + payload);
+ webSocketMessageHandlers.forEach(h -> h.handleWebSocketMessage((String) payload));
+ }
+ });
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Got exception when trying to connect", e);
}
-
- @Override
- public void handleFrame(StompHeaders headers, Object payload) {
- logger.log(Level.INFO, "Handling subscription frame: " + payload);
- webSocketMessageHandlers.forEach(h -> h.handleWebSocketMessage((String) payload));
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException e) {
+ logger.log(Level.WARNING, "Got exception when trying to connect", e);
}
- });
- });
+ }
+ }).start();
}
/**
- * Handler used to perform housekeeping...
+ * Handler used to perform housekeeping, e.g. trigger reconnection attempts if connection goes down.
*/
private class StompSessionHandler extends StompSessionHandlerAdapter {
@@ -151,6 +182,7 @@ public void handleFrame(StompHeaders headers, @Nullable Object payload) {
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
+ attemptReconnect.set(false);
logger.log(Level.INFO, "Connected to web socket");
if (connectCallback != null) {
connectCallback.run();
@@ -163,9 +195,22 @@ public void handleException(StompSession session, @Nullable StompCommand command
logger.log(Level.WARNING, "Exception encountered", exception);
}
+ /**
+ * Note that this is called both on connection failure and if remote web socket peer
+ * goes away for whatever reason.
+ * @param session the client STOMP session
+ * @param exception the exception that occurred. This is evaluated to determine if a reconnection
+ * thread should be launched.
+ */
@Override
public void handleTransportError(StompSession session, Throwable exception) {
- logger.log(Level.WARNING, "Handling web socket transport error: " + exception.getMessage(), exception);
+ if(exception instanceof ConnectionLostException){
+ logger.log(Level.WARNING, "Connection lost, will attempt to reconnect", exception);
+ doConnect();
+ }
+ else{
+ logger.log(Level.WARNING, "Handling transport exception", exception);
+ }
if (disconnectCallback != null) {
disconnectCallback.run();
}
From ed6379820cdac63320165121b42d320cb00c8225 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 27 Aug 2025 13:52:44 +0200
Subject: [PATCH 07/23] Adding Javadoc
---
.../olog/ui/LogEntryTableViewController.java | 2 -
.../olog/ui/LogbookSearchController.java | 2 -
.../WebSocketClientService.java | 58 +++++++++++++++----
3 files changed, 47 insertions(+), 15 deletions(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
index 7fe7b1f3cc..50edb5cbd6 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
@@ -701,8 +701,6 @@ public void handleWebSocketMessage(String message){
WebSocketMessage webSocketMessage = objectMapper.readValue(message, WebSocketMessage.class);
if(webSocketMessage.messageType().equals(MessageType.NEW_LOG_ENTRY)){
search();
- webSocketClientService.close();
- webSocketClientService.sendEcho("Hello World");
}
} catch (JsonProcessingException e) {
logger.log(Level.WARNING, "Unable to deserialize message \"" + message + "\"");
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
index bbdae65ed9..7cfdc87568 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
@@ -52,7 +52,6 @@ public LogClient getLogClient(){
* @param errorHandler Client side error handler that should notify user.
*/
public void search(Map searchParams, final Consumer resultHandler, final BiConsumer errorHandler) {
- cancelPeriodSearch();
logbookSearchJob = LogbookSearchJob.submit(this.client,
searchParams,
resultHandler,
@@ -96,7 +95,6 @@ private void cancelPeriodSearch() {
* Utility method to cancel any ongoing periodic search jobs.
*/
public void shutdown() {
- //cancelPeriodSearch();
if(stompSession != null && stompSession.isConnected()){
Logger.getLogger(LogbookSearchController.class.getName()).log(Level.INFO, "Disconnecting from web socket");
stompSession.disconnect();
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
index d6684ed75d..096e2c8a24 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -23,7 +23,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -66,8 +65,11 @@ public class WebSocketClientService {
private static final Logger logger = Logger.getLogger(WebSocketClientService.class.getName());
+ /**
+ * Constructor if connect/disconnect callbacks are not needed.
+ */
+ @SuppressWarnings("unused")
public WebSocketClientService() {
-
}
/**
@@ -80,10 +82,12 @@ public WebSocketClientService(Runnable connectCallback, Runnable disconnectCallb
this.disconnectCallback = disconnectCallback;
}
+ @SuppressWarnings("unused")
public void setConnectCallback(Runnable connectCallback) {
this.connectCallback = connectCallback;
}
+ @SuppressWarnings("unused")
public void setDisconnectCallback(Runnable disconnectCallback) {
this.disconnectCallback = disconnectCallback;
}
@@ -96,12 +100,16 @@ public void removeWebSocketMessageHandler(WebSocketMessageHandler webSocketMessa
webSocketMessageHandlers.remove(webSocketMessageHandler);
}
-
+ /**
+ * For debugging purposes: peer should just echo back the message on the subscribed topic.
+ * @param message Message to echo
+ */
+ @SuppressWarnings("unused")
public void sendEcho(String message) {
stompSession.send(contextPath + "/web-socket/echo", message);
}
- public void close(){
+ public void close() {
stompSession.disconnect();
}
@@ -129,6 +137,9 @@ public void connect(String baseUrl) {
doConnect();
}
+ /**
+ *
+ */
private void doConnect() {
attemptReconnect.set(true);
WebSocketClient webSocketClient = new StandardWebSocketClient();
@@ -173,13 +184,26 @@ public void handleFrame(StompHeaders headers, Object payload) {
*/
private class StompSessionHandler extends StompSessionHandlerAdapter {
+ /**
+ * Logs that web socket frame has been received.
+ *
+ * @param headers the headers of the frame
+ * @param payload the payload, or {@code null} if there was no payload
+ */
@Override
public void handleFrame(StompHeaders headers, @Nullable Object payload) {
if (payload != null) {
- logger.log(Level.INFO, "WebSocket frame received: " + payload);
+ logger.log(Level.FINE, "WebSocket frame received: " + payload);
}
}
+ /**
+ * Handles connection success callback: thread to attempt connection is aborted,
+ * and connect callback is called, if set by API client.
+ *
+ * @param session the client STOMP session
+ * @param connectedHeaders the STOMP CONNECTED frame headers
+ */
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
attemptReconnect.set(false);
@@ -189,6 +213,15 @@ public void afterConnected(StompSession session, StompHeaders connectedHeaders)
}
}
+ /**
+ * Hit for instance if an attempt is made to send a message to peer after {@link StompSession} has been closed.
+ *
+ * @param session the client STOMP session
+ * @param command the STOMP command of the frame
+ * @param headers the headers
+ * @param payload the raw payload
+ * @param exception the exception
+ */
@Override
public void handleException(StompSession session, @Nullable StompCommand command,
StompHeaders headers, byte[] payload, Throwable exception) {
@@ -196,19 +229,22 @@ public void handleException(StompSession session, @Nullable StompCommand command
}
/**
- * Note that this is called both on connection failure and if remote web socket peer
- * goes away for whatever reason.
- * @param session the client STOMP session
+ * If remote peer goes away because the service is shut down, or because
+ * of a network connection issue, we get a {@link ConnectionLostException}. In this case
+ * a reconnection thread is started. If on the other hand a connection attempt fails, we get
+ * a different type of exception (javax.websocket.DeploymentException), in which case a
+ * reconnection thread is not started.
+ *
+ * @param session the client STOMP session
* @param exception the exception that occurred. This is evaluated to determine if a reconnection
* thread should be launched.
*/
@Override
public void handleTransportError(StompSession session, Throwable exception) {
- if(exception instanceof ConnectionLostException){
+ if (exception instanceof ConnectionLostException) {
logger.log(Level.WARNING, "Connection lost, will attempt to reconnect", exception);
doConnect();
- }
- else{
+ } else {
logger.log(Level.WARNING, "Handling transport exception", exception);
}
if (disconnectCallback != null) {
From c782641c2038bf7caaf049165b90661126a94b3f Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 27 Aug 2025 15:02:12 +0200
Subject: [PATCH 08/23] Added web socket to log calendar app, fixed shutdown of
web sockets
---
.../ui/LogEntryCalenderViewController.java | 101 +++++++++++++++---
.../olog/ui/LogEntryTableViewController.java | 23 ++--
.../olog/ui/LogbookSearchController.java | 39 +------
.../logbook/olog/ui/LogEntryCalenderView.fxml | 26 +++--
.../WebSocketClientService.java | 2 +-
5 files changed, 120 insertions(+), 71 deletions(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
index 20530f9750..72a8af0080 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
@@ -1,9 +1,12 @@
package org.phoebus.logbook.olog.ui;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
+import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -12,6 +15,7 @@
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
+import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.input.KeyCode;
@@ -28,14 +32,20 @@
import jfxtras.scene.control.agenda.Agenda.Appointment;
import jfxtras.scene.control.agenda.Agenda.AppointmentGroup;
import jfxtras.scene.control.agenda.Agenda.AppointmentImplLocal;
+import org.phoebus.core.websocket.WebSocketMessageHandler;
+import org.phoebus.core.websocket.springframework.WebSocketClientService;
import org.phoebus.framework.nls.NLS;
import org.phoebus.logbook.LogClient;
import org.phoebus.logbook.LogEntry;
import org.phoebus.logbook.SearchResult;
import org.phoebus.logbook.olog.ui.query.OlogQuery;
import org.phoebus.logbook.olog.ui.query.OlogQueryManager;
+import org.phoebus.logbook.olog.ui.websocket.MessageType;
+import org.phoebus.logbook.olog.ui.websocket.WebSocketMessage;
+import org.phoebus.olog.es.api.Preferences;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
+import java.net.URI;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -55,7 +65,7 @@
*
* @author Kunal Shroff
*/
-public class LogEntryCalenderViewController extends LogbookSearchController {
+public class LogEntryCalenderViewController extends LogbookSearchController implements WebSocketMessageHandler {
private static final Logger logger = Logger.getLogger(LogEntryCalenderViewController.class.getName());
@@ -73,6 +83,7 @@ public class LogEntryCalenderViewController extends LogbookSearchController {
// Model
List logEntries;
+ @SuppressWarnings("unused")
@FXML
private AnchorPane agendaPane;
@FXML
@@ -80,14 +91,21 @@ public class LogEntryCalenderViewController extends LogbookSearchController {
// Model
private Map map;
- private Map appointmentGroupMap = new TreeMap();
+ private Map appointmentGroupMap = new TreeMap<>();
+ @SuppressWarnings("unused")
@FXML
private AdvancedSearchViewController advancedSearchViewController;
+ @SuppressWarnings("unused")
+ @FXML
+ private Label autoUpdateStatusLabel;
+
private final OlogQueryManager ologQueryManager;
private final ObservableList ologQueries = FXCollections.observableArrayList();
- private SearchParameters searchParameters;
+ private final SearchParameters searchParameters;
+ private final SimpleBooleanProperty webSocketConnected = new SimpleBooleanProperty();
+ private final ObjectMapper objectMapper = new ObjectMapper();
public LogEntryCalenderViewController(LogClient logClient, OlogQueryManager ologQueryManager, SearchParameters searchParameters) {
setClient(logClient);
@@ -103,9 +121,7 @@ public void initialize() {
// Set the search parameters in the advanced search controller so that it operates on the same object.
ologQueries.setAll(ologQueryManager.getQueries());
- searchParameters.addListener((observable, oldValue, newValue) -> {
- query.getEditor().setText(newValue);
- });
+ searchParameters.addListener((observable, oldValue, newValue) -> query.getEditor().setText(newValue));
agenda = new Agenda();
agenda.setEditAppointmentCallback(new Callback() {
@@ -195,13 +211,29 @@ public Void call(Appointment appointment) {
search.disableProperty().bind(searchInProgress);
+ webSocketConnected.addListener((obs, o, n) -> {
+ Platform.runLater(() -> {
+ if(n){
+ autoUpdateStatusLabel.setStyle("-fx-text-fill: black;");
+ autoUpdateStatusLabel.setText(Messages.AutoRefreshOn);
+ }
+ else{
+ autoUpdateStatusLabel.setStyle("-fx-text-fill: red;");
+ autoUpdateStatusLabel.setText(Messages.AutoRefreshOff);
+ }
+ });
+ });
+
+ connectWebSocket();
+
search();
}
// Keeps track of when the animation is active. Multiple clicks will be ignored
// until a give resize action is completed
- private AtomicBoolean moving = new AtomicBoolean(false);
+ private final AtomicBoolean moving = new AtomicBoolean(false);
+ @SuppressWarnings("unused")
@FXML
public void resize() {
if (!moving.compareAndExchangeAcquire(false, true)) {
@@ -246,8 +278,6 @@ public void search() {
searchResult1 -> {
searchInProgress.set(false);
setSearchResult(searchResult1);
- logger.log(Level.INFO, "Starting periodic search: " + queryString);
- periodicSearch(params, searchResult -> setSearchResult(searchResult));
List queries = ologQueryManager.getQueries();
Platform.runLater(() -> {
ologQueries.setAll(queries);
@@ -285,11 +315,15 @@ public Appointment apply(LogEntry logentry) {
LocalDateTime.ofInstant(logentry.getCreatedDate().plusSeconds(2400), ZoneId.systemDefault()));
List logbookNames = getLogbookNames();
if (logbookNames != null && !logbookNames.isEmpty()) {
- int index = logbookNames.indexOf(logentry.getLogbooks().iterator().next().getName());
- if (index >= 0 && index <= 22) {
- appointment.setAppointmentGroup(appointmentGroupMap.get(String.format("group%02d", (index + 1))));
- } else {
- appointment.setAppointmentGroup(appointmentGroupMap.get(String.format("group%02d", 23)));
+ try {
+ int index = logbookNames.indexOf(logentry.getLogbooks().iterator().next().getName());
+ if (index >= 0 && index <= 22) {
+ appointment.setAppointmentGroup(appointmentGroupMap.get(String.format("group%02d", (index + 1))));
+ } else {
+ appointment.setAppointmentGroup(appointmentGroupMap.get(String.format("group%02d", 23)));
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
}
return appointment;
@@ -318,7 +352,8 @@ private List getLogbookNames() {
}
private void setSearchResult(SearchResult searchResult) {
- setLogs(searchResult.getLogs());List queries = ologQueryManager.getQueries();
+ setLogs(searchResult.getLogs());
+ List queries = ologQueryManager.getQueries();
Platform.runLater(() -> {
ologQueries.setAll(queries);
// Top-most query is the one used in the search.
@@ -376,4 +411,40 @@ public OlogQuery fromString(String s) {
});
}
+
+ private void connectWebSocket(){
+ String baseUrl = Preferences.olog_url;
+ URI uri = URI.create(baseUrl);
+ String scheme = uri.getScheme();
+ String host = uri.getHost();
+ int port = uri.getPort();
+ String path = uri.getPath();
+ if(path.endsWith("/")){
+ path = path.substring(0, path.length() - 1);
+ }
+ String webSocketScheme = scheme.toLowerCase().startsWith("https") ? "wss" : "ws";
+ String webSocketUrl = webSocketScheme + "://" + host + (port > -1 ? (":" + port) : "") + path;
+
+ webSocketClientService = new WebSocketClientService(() -> {
+ logger.log(Level.INFO, "Connected to web socket on " + webSocketUrl);
+ webSocketConnected.set(true);
+ }, () -> {
+ logger.log(Level.INFO, "Disconnected from web socket on " + webSocketUrl);
+ webSocketConnected.set(false);
+ });
+ webSocketClientService.addWebSocketMessageHandler(this);
+ webSocketClientService.connect(webSocketUrl);
+ }
+
+ @Override
+ public void handleWebSocketMessage(String message){
+ try {
+ WebSocketMessage webSocketMessage = objectMapper.readValue(message, WebSocketMessage.class);
+ if(webSocketMessage.messageType().equals(MessageType.NEW_LOG_ENTRY)){
+ search();
+ }
+ } catch (JsonProcessingException e) {
+ logger.log(Level.WARNING, "Unable to deserialize message \"" + message + "\"");
+ }
+ }
}
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
index 50edb5cbd6..5c72836525 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
@@ -142,7 +142,6 @@ public class LogEntryTableViewController extends LogbookSearchController impleme
private final SimpleBooleanProperty advancedSearchVisible = new SimpleBooleanProperty(false);
- private WebSocketClientService webSocketClientService;
/**
* Constructor.
@@ -340,14 +339,16 @@ public void updateItem(TableViewListItem logEntry, boolean empty) {
advancedSearchVisible));
webSocketConnected.addListener((obs, o, n) -> {
- if(n){
- autoUpdateStatusLabel.setStyle("-fx-text-fill: black;");
- autoUpdateStatusLabel.setText(Messages.AutoRefreshOn);
- }
- else{
- autoUpdateStatusLabel.setStyle("-fx-text-fill: red;");
- autoUpdateStatusLabel.setText(Messages.AutoRefreshOff);
- }
+ Platform.runLater(() -> {
+ if(n){
+ autoUpdateStatusLabel.setStyle("-fx-text-fill: black;");
+ autoUpdateStatusLabel.setText(Messages.AutoRefreshOn);
+ }
+ else{
+ autoUpdateStatusLabel.setStyle("-fx-text-fill: red;");
+ autoUpdateStatusLabel.setText(Messages.AutoRefreshOff);
+ }
+ });
});
connectWebSocket();
@@ -686,10 +687,10 @@ private void connectWebSocket(){
webSocketClientService = new WebSocketClientService(() -> {
logger.log(Level.INFO, "Connected to web socket on " + webSocketUrl);
- Platform.runLater(() -> webSocketConnected.set(true));
+ webSocketConnected.set(true);
}, () -> {
logger.log(Level.INFO, "Disconnected from web socket on " + webSocketUrl);
- Platform.runLater(() -> webSocketConnected.set(false));
+ webSocketConnected.set(false);
});
webSocketClientService.addWebSocketMessageHandler(this);
webSocketClientService.connect(webSocketUrl);
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
index 7cfdc87568..7ebc94df2e 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
@@ -1,18 +1,17 @@
package org.phoebus.logbook.olog.ui;
import javafx.beans.property.SimpleBooleanProperty;
+import org.phoebus.core.websocket.springframework.WebSocketClientService;
import org.phoebus.framework.jobs.Job;
import org.phoebus.logbook.LogClient;
import org.phoebus.logbook.LogEntry;
import org.phoebus.logbook.SearchResult;
-import org.springframework.messaging.simp.stomp.StompSession;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.logging.Level;
@@ -35,7 +34,7 @@ public abstract class LogbookSearchController {
protected final SimpleBooleanProperty searchInProgress = new SimpleBooleanProperty(false);
private static final int SEARCH_JOB_INTERVAL = 30; // seconds
- protected StompSession stompSession;
+ protected WebSocketClientService webSocketClientService;
public void setClient(LogClient client) {
this.client = client;
@@ -58,46 +57,16 @@ public void search(Map searchParams, final Consumer searchParams, final Consumer resultHandler) {
- cancelPeriodSearch();
- runningTask = executor.scheduleAtFixedRate(() -> logbookSearchJob = LogbookSearchJob.submit(this.client,
- searchParams,
- resultHandler,
- (url, ex) -> {
- searchInProgress.set(false);
- cancelPeriodSearch();
- }), SEARCH_JOB_INTERVAL, SEARCH_JOB_INTERVAL, TimeUnit.SECONDS);
- }
-
@Deprecated
public abstract void setLogs(List logs);
- /**
- * Stops periodic search and ongoing search jobs, if any.
- */
- private void cancelPeriodSearch() {
- if (runningTask != null) {
- runningTask.cancel(true);
- }
-
- if (logbookSearchJob != null) {
- logbookSearchJob.cancel();
- }
- }
-
/**
* Utility method to cancel any ongoing periodic search jobs.
*/
public void shutdown() {
- if(stompSession != null && stompSession.isConnected()){
+ if(webSocketClientService != null){
Logger.getLogger(LogbookSearchController.class.getName()).log(Level.INFO, "Disconnecting from web socket");
- stompSession.disconnect();
+ webSocketClientService.disconnect();
}
}
}
diff --git a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryCalenderView.fxml b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryCalenderView.fxml
index 0c30107326..022563b632 100644
--- a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryCalenderView.fxml
+++ b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryCalenderView.fxml
@@ -4,29 +4,37 @@
-
+
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
-
+
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
index 096e2c8a24..aca659d8f8 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -109,7 +109,7 @@ public void sendEcho(String message) {
stompSession.send(contextPath + "/web-socket/echo", message);
}
- public void close() {
+ public void disconnect() {
stompSession.disconnect();
}
From b916b3ad595837c23a5949ccee7559b5eea928af Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 27 Aug 2025 15:14:46 +0200
Subject: [PATCH 09/23] Refactor pom, add Spring websocket related stuff to
phoebus platform
---
dependencies/phoebus-target/pom.xml | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/dependencies/phoebus-target/pom.xml b/dependencies/phoebus-target/pom.xml
index f8092396ee..0e1e766014 100644
--- a/dependencies/phoebus-target/pom.xml
+++ b/dependencies/phoebus-target/pom.xml
@@ -447,6 +447,17 @@
${spring.boot-version}test
+
+ org.springframework
+ spring-websocket
+ ${spring.framework.version}
+
+
+ org.springframework
+ spring-messaging
+ ${spring.framework.version}
+
+
org.springdocspringdoc-openapi-ui
From 5069f29faa0e9c42ff1ec09269388a986f05011a Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 27 Aug 2025 15:34:21 +0200
Subject: [PATCH 10/23] Refactoring to avoid code duplication
---
.../ui/LogEntryCalenderViewController.java | 70 +--------------
.../olog/ui/LogEntryTableViewController.java | 70 +--------------
.../olog/ui/LogbookSearchController.java | 89 ++++++++++++++++---
core/websocket/pom.xml | 4 -
pom.xml | 1 +
5 files changed, 85 insertions(+), 149 deletions(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
index 72a8af0080..3505dc1401 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
@@ -1,12 +1,9 @@
package org.phoebus.logbook.olog.ui;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
-import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -15,7 +12,6 @@
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
-import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.input.KeyCode;
@@ -32,20 +28,14 @@
import jfxtras.scene.control.agenda.Agenda.Appointment;
import jfxtras.scene.control.agenda.Agenda.AppointmentGroup;
import jfxtras.scene.control.agenda.Agenda.AppointmentImplLocal;
-import org.phoebus.core.websocket.WebSocketMessageHandler;
-import org.phoebus.core.websocket.springframework.WebSocketClientService;
import org.phoebus.framework.nls.NLS;
import org.phoebus.logbook.LogClient;
import org.phoebus.logbook.LogEntry;
import org.phoebus.logbook.SearchResult;
import org.phoebus.logbook.olog.ui.query.OlogQuery;
import org.phoebus.logbook.olog.ui.query.OlogQueryManager;
-import org.phoebus.logbook.olog.ui.websocket.MessageType;
-import org.phoebus.logbook.olog.ui.websocket.WebSocketMessage;
-import org.phoebus.olog.es.api.Preferences;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
-import java.net.URI;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -65,7 +55,7 @@
*
* @author Kunal Shroff
*/
-public class LogEntryCalenderViewController extends LogbookSearchController implements WebSocketMessageHandler {
+public class LogEntryCalenderViewController extends LogbookSearchController {
private static final Logger logger = Logger.getLogger(LogEntryCalenderViewController.class.getName());
@@ -97,15 +87,9 @@ public class LogEntryCalenderViewController extends LogbookSearchController impl
@FXML
private AdvancedSearchViewController advancedSearchViewController;
- @SuppressWarnings("unused")
- @FXML
- private Label autoUpdateStatusLabel;
-
private final OlogQueryManager ologQueryManager;
private final ObservableList ologQueries = FXCollections.observableArrayList();
private final SearchParameters searchParameters;
- private final SimpleBooleanProperty webSocketConnected = new SimpleBooleanProperty();
- private final ObjectMapper objectMapper = new ObjectMapper();
public LogEntryCalenderViewController(LogClient logClient, OlogQueryManager ologQueryManager, SearchParameters searchParameters) {
setClient(logClient);
@@ -114,7 +98,9 @@ public LogEntryCalenderViewController(LogClient logClient, OlogQueryManager olog
}
@FXML
+ @Override
public void initialize() {
+ super.initialize();
advancedSearchViewController.setSearchCallback(this::search);
configureComboBox();
@@ -211,19 +197,6 @@ public Void call(Appointment appointment) {
search.disableProperty().bind(searchInProgress);
- webSocketConnected.addListener((obs, o, n) -> {
- Platform.runLater(() -> {
- if(n){
- autoUpdateStatusLabel.setStyle("-fx-text-fill: black;");
- autoUpdateStatusLabel.setText(Messages.AutoRefreshOn);
- }
- else{
- autoUpdateStatusLabel.setStyle("-fx-text-fill: red;");
- autoUpdateStatusLabel.setText(Messages.AutoRefreshOff);
- }
- });
- });
-
connectWebSocket();
search();
@@ -268,6 +241,7 @@ public void resize() {
}
@FXML
+ @Override
public void search() {
String queryString = query.getEditor().getText();
Map params =
@@ -411,40 +385,4 @@ public OlogQuery fromString(String s) {
});
}
-
- private void connectWebSocket(){
- String baseUrl = Preferences.olog_url;
- URI uri = URI.create(baseUrl);
- String scheme = uri.getScheme();
- String host = uri.getHost();
- int port = uri.getPort();
- String path = uri.getPath();
- if(path.endsWith("/")){
- path = path.substring(0, path.length() - 1);
- }
- String webSocketScheme = scheme.toLowerCase().startsWith("https") ? "wss" : "ws";
- String webSocketUrl = webSocketScheme + "://" + host + (port > -1 ? (":" + port) : "") + path;
-
- webSocketClientService = new WebSocketClientService(() -> {
- logger.log(Level.INFO, "Connected to web socket on " + webSocketUrl);
- webSocketConnected.set(true);
- }, () -> {
- logger.log(Level.INFO, "Disconnected from web socket on " + webSocketUrl);
- webSocketConnected.set(false);
- });
- webSocketClientService.addWebSocketMessageHandler(this);
- webSocketClientService.connect(webSocketUrl);
- }
-
- @Override
- public void handleWebSocketMessage(String message){
- try {
- WebSocketMessage webSocketMessage = objectMapper.readValue(message, WebSocketMessage.class);
- if(webSocketMessage.messageType().equals(MessageType.NEW_LOG_ENTRY)){
- search();
- }
- } catch (JsonProcessingException e) {
- logger.log(Level.WARNING, "Unable to deserialize message \"" + message + "\"");
- }
- }
}
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
index 5c72836525..df3830d803 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
@@ -1,7 +1,5 @@
package org.phoebus.logbook.olog.ui;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
@@ -42,7 +40,6 @@
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.phoebus.core.websocket.WebSocketMessageHandler;
-import org.phoebus.core.websocket.springframework.WebSocketClientService;
import org.phoebus.framework.jobs.JobManager;
import org.phoebus.logbook.LogClient;
import org.phoebus.logbook.LogEntry;
@@ -53,11 +50,8 @@
import org.phoebus.logbook.olog.ui.query.OlogQuery;
import org.phoebus.logbook.olog.ui.query.OlogQueryManager;
import org.phoebus.logbook.olog.ui.spi.Decoration;
-import org.phoebus.logbook.olog.ui.websocket.MessageType;
-import org.phoebus.logbook.olog.ui.websocket.WebSocketMessage;
import org.phoebus.logbook.olog.ui.write.EditMode;
import org.phoebus.logbook.olog.ui.write.LogEntryEditorStage;
-import org.phoebus.olog.es.api.Preferences;
import org.phoebus.olog.es.api.model.LogGroupProperty;
import org.phoebus.olog.es.api.model.OlogLog;
import org.phoebus.security.store.SecureStore;
@@ -67,7 +61,6 @@
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
import java.io.IOException;
-import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -125,13 +118,6 @@ public class LogEntryTableViewController extends LogbookSearchController impleme
// Model
private SearchResult searchResult;
- private ObjectMapper objectMapper = new ObjectMapper();
-
- @FXML
- private Label autoUpdateStatusLabel;
-
- private SimpleBooleanProperty webSocketConnected = new SimpleBooleanProperty();
-
/**
* List of selected log entries
*/
@@ -173,7 +159,9 @@ protected void setGoBackAndGoForwardActions(LogEntryTable.GoBackAndGoForwardActi
protected Optional goBackAndGoForwardActions = Optional.empty();
@FXML
+ @Override
public void initialize() {
+ super.initialize();
logEntryDisplayController.setLogEntryTableViewController(this);
@@ -338,19 +326,6 @@ public void updateItem(TableViewListItem logEntry, boolean empty) {
Messages.AdvancedSearchHide : Messages.AdvancedSearchOpen,
advancedSearchVisible));
- webSocketConnected.addListener((obs, o, n) -> {
- Platform.runLater(() -> {
- if(n){
- autoUpdateStatusLabel.setStyle("-fx-text-fill: black;");
- autoUpdateStatusLabel.setText(Messages.AutoRefreshOn);
- }
- else{
- autoUpdateStatusLabel.setStyle("-fx-text-fill: red;");
- autoUpdateStatusLabel.setText(Messages.AutoRefreshOff);
- }
- });
- });
-
connectWebSocket();
search();
@@ -396,6 +371,7 @@ public void resize() {
* the UI is updated and a periodic search is launched using the same query. If on the other hand
* the search fails (service off-line or invalid query), a periodic search is NOT launched.
*/
+ @Override
public void search() {
// In case the page size text field is empty, or the value is zero, set the page size to the default
if ("".equals(pageSizeTextField.getText()) || Integer.parseInt(pageSizeTextField.getText()) == 0) {
@@ -420,8 +396,6 @@ public void search() {
searchResult1 -> {
searchInProgress.set(false);
setSearchResult(searchResult1);
- //logger.log(Level.INFO, "Starting periodic search: " + queryString);
- //periodicSearch(params, this::setSearchResult);
List queries = ologQueryManager.getQueries();
Platform.runLater(() -> {
ologQueries.setAll(queries);
@@ -641,7 +615,7 @@ public void logEntryChanged(LogEntry logEntry) {
setLogEntry(logEntry);
}
- private void logEntryChanged(String logEntryId){
+ private void logEntryChanged(String logEntryId) {
search();
}
@@ -671,40 +645,4 @@ public boolean selectLogEntry(LogEntry logEntry) {
}
return false;
}
-
- private void connectWebSocket(){
- String baseUrl = Preferences.olog_url;
- URI uri = URI.create(baseUrl);
- String scheme = uri.getScheme();
- String host = uri.getHost();
- int port = uri.getPort();
- String path = uri.getPath();
- if(path.endsWith("/")){
- path = path.substring(0, path.length() - 1);
- }
- String webSocketScheme = scheme.toLowerCase().startsWith("https") ? "wss" : "ws";
- String webSocketUrl = webSocketScheme + "://" + host + (port > -1 ? (":" + port) : "") + path;
-
- webSocketClientService = new WebSocketClientService(() -> {
- logger.log(Level.INFO, "Connected to web socket on " + webSocketUrl);
- webSocketConnected.set(true);
- }, () -> {
- logger.log(Level.INFO, "Disconnected from web socket on " + webSocketUrl);
- webSocketConnected.set(false);
- });
- webSocketClientService.addWebSocketMessageHandler(this);
- webSocketClientService.connect(webSocketUrl);
- }
-
- @Override
- public void handleWebSocketMessage(String message){
- try {
- WebSocketMessage webSocketMessage = objectMapper.readValue(message, WebSocketMessage.class);
- if(webSocketMessage.messageType().equals(MessageType.NEW_LOG_ENTRY)){
- search();
- }
- } catch (JsonProcessingException e) {
- logger.log(Level.WARNING, "Unable to deserialize message \"" + message + "\"");
- }
- }
}
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
index 7ebc94df2e..7a1ec7ba54 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
@@ -1,17 +1,23 @@
package org.phoebus.logbook.olog.ui;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
import javafx.beans.property.SimpleBooleanProperty;
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import org.phoebus.core.websocket.WebSocketMessageHandler;
import org.phoebus.core.websocket.springframework.WebSocketClientService;
import org.phoebus.framework.jobs.Job;
import org.phoebus.logbook.LogClient;
import org.phoebus.logbook.LogEntry;
import org.phoebus.logbook.SearchResult;
+import org.phoebus.logbook.olog.ui.websocket.MessageType;
+import org.phoebus.logbook.olog.ui.websocket.WebSocketMessage;
+import org.phoebus.olog.es.api.Preferences;
+import java.net.URI;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.logging.Level;
@@ -25,33 +31,52 @@
*
* @author Kunal Shroff
*/
-public abstract class LogbookSearchController {
+public abstract class LogbookSearchController implements WebSocketMessageHandler {
- private Job logbookSearchJob;
protected LogClient client;
- private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
- private ScheduledFuture> runningTask;
protected final SimpleBooleanProperty searchInProgress = new SimpleBooleanProperty(false);
- private static final int SEARCH_JOB_INTERVAL = 30; // seconds
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+ private final Logger logger = Logger.getLogger(LogbookSearchController.class.getName());
+ private final SimpleBooleanProperty webSocketConnected = new SimpleBooleanProperty();
+
+ @SuppressWarnings("unused")
+ @FXML
+ private Label autoUpdateStatusLabel;
protected WebSocketClientService webSocketClientService;
+ @FXML
+ public void initialize(){
+ webSocketConnected.addListener((obs, o, n) -> {
+ if(n){
+ autoUpdateStatusLabel.setStyle("-fx-text-fill: black;");
+ autoUpdateStatusLabel.setText(Messages.AutoRefreshOn);
+ }
+ else{
+ autoUpdateStatusLabel.setStyle("-fx-text-fill: red;");
+ autoUpdateStatusLabel.setText(Messages.AutoRefreshOff);
+ }
+ });
+ }
+
public void setClient(LogClient client) {
this.client = client;
}
- public LogClient getLogClient(){
+ public LogClient getLogClient() {
return client;
}
/**
* Starts a single search job. This should be used to search once.
- * @param searchParams The search parameters
+ *
+ * @param searchParams The search parameters
* @param resultHandler Handler taking care of the search result.
- * @param errorHandler Client side error handler that should notify user.
+ * @param errorHandler Client side error handler that should notify user.
*/
public void search(Map searchParams, final Consumer resultHandler, final BiConsumer errorHandler) {
- logbookSearchJob = LogbookSearchJob.submit(this.client,
+ LogbookSearchJob.submit(this.client,
searchParams,
resultHandler,
errorHandler);
@@ -64,9 +89,47 @@ public void search(Map searchParams, final Consumer -1 ? (":" + port) : "") + path;
+
+ webSocketClientService = new WebSocketClientService(() -> {
+ logger.log(Level.INFO, "Connected to web socket on " + webSocketUrl);
+ webSocketConnected.set(true);
+ }, () -> {
+ logger.log(Level.INFO, "Disconnected from web socket on " + webSocketUrl);
+ webSocketConnected.set(false);
+ });
+ webSocketClientService.addWebSocketMessageHandler(this);
+ webSocketClientService.connect(webSocketUrl);
+ }
+
+ @Override
+ public void handleWebSocketMessage(String message) {
+ try {
+ WebSocketMessage webSocketMessage = objectMapper.readValue(message, WebSocketMessage.class);
+ if (webSocketMessage.messageType().equals(MessageType.NEW_LOG_ENTRY)) {
+ search();
+ }
+ } catch (JsonProcessingException e) {
+ logger.log(Level.WARNING, "Unable to deserialize message \"" + message + "\"");
+ }
+ }
}
diff --git a/core/websocket/pom.xml b/core/websocket/pom.xml
index cc29c6313b..ab45ff67b8 100644
--- a/core/websocket/pom.xml
+++ b/core/websocket/pom.xml
@@ -12,10 +12,6 @@
5.0.3-SNAPSHOT
-
- 5.3.22
-
-
org.phoebus
diff --git a/pom.xml b/pom.xml
index 2aaff63f99..dcb29fed3b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -92,6 +92,7 @@
175.18.41.26.1
+ 5.3.22
From 59dfd73ba43220348494fb8ec5e0916e81014229 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 27 Aug 2025 15:51:34 +0200
Subject: [PATCH 11/23] Random sleep before Olog clients refresh search
triggered by web socket message
---
.../phoebus/logbook/olog/ui/LogbookSearchController.java | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
index 7a1ec7ba54..995f50bb17 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
@@ -126,6 +126,13 @@ public void handleWebSocketMessage(String message) {
try {
WebSocketMessage webSocketMessage = objectMapper.readValue(message, WebSocketMessage.class);
if (webSocketMessage.messageType().equals(MessageType.NEW_LOG_ENTRY)) {
+ // Add a random sleep 0 - 5 seconds to avoid an avalanche of search requests on the service.
+ long randomSleepTime = Math.round(5000 * Math.random());
+ try {
+ Thread.sleep(randomSleepTime);
+ } catch (InterruptedException e) {
+ logger.log(Level.WARNING, "Got exception when sleeping before search request", e);
+ }
search();
}
} catch (JsonProcessingException e) {
From f60ab8ff504c0e3bc5dd8c97c84906a29cf53cfb Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Fri, 29 Aug 2025 15:39:02 +0200
Subject: [PATCH 12/23] Aligned with save&restore: if service is unavailable,
disable the UI and show message (not in dialog)
---
.../ui/LogEntryCalenderViewController.java | 2 -
.../olog/ui/LogEntryTableViewController.java | 23 +++---
.../olog/ui/LogbookSearchController.java | 32 ++++----
.../org/phoebus/logbook/olog/ui/Messages.java | 2 -
.../logbook/olog/ui/LogEntryCalenderView.fxml | 76 +++++++++++--------
.../logbook/olog/ui/LogEntryTableView.fxml | 38 +++++-----
.../logbook/olog/ui/messages.properties | 3 +-
.../WebSocketClientService.java | 14 +++-
8 files changed, 104 insertions(+), 86 deletions(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
index 3505dc1401..7d3e96b697 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
@@ -34,7 +34,6 @@
import org.phoebus.logbook.SearchResult;
import org.phoebus.logbook.olog.ui.query.OlogQuery;
import org.phoebus.logbook.olog.ui.query.OlogQueryManager;
-import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
import java.net.URL;
import java.time.LocalDateTime;
@@ -260,7 +259,6 @@ public void search() {
},
(msg, ex) -> {
searchInProgress.set(false);
- ExceptionDetailsErrorDialog.openError("Logbook Search Error", ex);
});
}
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
index df3830d803..10588f44a2 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
@@ -34,6 +34,8 @@
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.GridPane;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.util.Callback;
@@ -58,7 +60,6 @@
import org.phoebus.security.tokens.AuthenticationScope;
import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.dialog.DialogHelper;
-import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
import java.io.IOException;
import java.util.ArrayList;
@@ -112,6 +113,14 @@ public class LogEntryTableViewController extends LogbookSearchController impleme
@SuppressWarnings("unused")
private TextField pageSizeTextField;
+ @SuppressWarnings("unused")
+ @FXML
+ private Pane logDetailView;
+
+ @SuppressWarnings("unused")
+ @FXML
+ private VBox errorPane;
+
@FXML
@SuppressWarnings("unused")
private Label openAdvancedSearchLabel;
@@ -125,7 +134,6 @@ public class LogEntryTableViewController extends LogbookSearchController impleme
private static final Logger logger = Logger.getLogger(LogEntryTableViewController.class.getName());
private final SimpleBooleanProperty showDetails = new SimpleBooleanProperty();
-
private final SimpleBooleanProperty advancedSearchVisible = new SimpleBooleanProperty(false);
@@ -175,6 +183,7 @@ public void initialize() {
});
tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
+ tableView.visibleProperty().bind(serviceConnected);
MenuItem groupSelectedEntries = new MenuItem(Messages.GroupSelectedEntries);
groupSelectedEntries.setOnAction(e -> createLogEntryGroup());
@@ -326,9 +335,9 @@ public void updateItem(TableViewListItem logEntry, boolean empty) {
Messages.AdvancedSearchHide : Messages.AdvancedSearchOpen,
advancedSearchVisible));
- connectWebSocket();
+ logDetailView.disableProperty().bind(serviceConnected.not());
- search();
+ connectWebSocket();
}
// Keeps track of when the animation is active. Multiple clicks will be ignored
@@ -404,7 +413,7 @@ public void search() {
},
(msg, ex) -> {
searchInProgress.set(false);
- ExceptionDetailsErrorDialog.openError(Messages.LogbooksSearchFailTitle, ex.getMessage(), null);
+ //ExceptionDetailsErrorDialog.openError(Messages.LogbooksSearchFailTitle, ex.getMessage(), null);
});
}
@@ -615,10 +624,6 @@ public void logEntryChanged(LogEntry logEntry) {
setLogEntry(logEntry);
}
- private void logEntryChanged(String logEntryId) {
- search();
- }
-
protected LogEntry getLogEntry() {
return logEntryDisplayController.getLogEntry();
}
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
index 995f50bb17..408d136f8f 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
@@ -4,7 +4,8 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
-import javafx.scene.control.Label;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.VBox;
import org.phoebus.core.websocket.WebSocketMessageHandler;
import org.phoebus.core.websocket.springframework.WebSocketClientService;
import org.phoebus.framework.jobs.Job;
@@ -38,26 +39,22 @@ public abstract class LogbookSearchController implements WebSocketMessageHandler
private final ObjectMapper objectMapper = new ObjectMapper();
private final Logger logger = Logger.getLogger(LogbookSearchController.class.getName());
- private final SimpleBooleanProperty webSocketConnected = new SimpleBooleanProperty();
+ protected final SimpleBooleanProperty serviceConnected = new SimpleBooleanProperty();
+
+ protected WebSocketClientService webSocketClientService;
@SuppressWarnings("unused")
@FXML
- private Label autoUpdateStatusLabel;
+ private VBox errorPane;
- protected WebSocketClientService webSocketClientService;
+ @SuppressWarnings("unused")
+ @FXML
+ private GridPane ViewSearchPane;
@FXML
- public void initialize(){
- webSocketConnected.addListener((obs, o, n) -> {
- if(n){
- autoUpdateStatusLabel.setStyle("-fx-text-fill: black;");
- autoUpdateStatusLabel.setText(Messages.AutoRefreshOn);
- }
- else{
- autoUpdateStatusLabel.setStyle("-fx-text-fill: red;");
- autoUpdateStatusLabel.setText(Messages.AutoRefreshOff);
- }
- });
+ public void initialize() {
+ errorPane.visibleProperty().bind(serviceConnected.not());
+ ViewSearchPane.visibleProperty().bind(serviceConnected);
}
public void setClient(LogClient client) {
@@ -112,10 +109,11 @@ protected void connectWebSocket() {
webSocketClientService = new WebSocketClientService(() -> {
logger.log(Level.INFO, "Connected to web socket on " + webSocketUrl);
- webSocketConnected.set(true);
+ serviceConnected.setValue(true);
+ search();
}, () -> {
logger.log(Level.INFO, "Disconnected from web socket on " + webSocketUrl);
- webSocketConnected.set(false);
+ serviceConnected.set(false);
});
webSocketClientService.addWebSocketMessageHandler(this);
webSocketClientService.connect(webSocketUrl);
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/Messages.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/Messages.java
index b92638a38b..60d2bd92cc 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/Messages.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/Messages.java
@@ -23,8 +23,6 @@ public class Messages
AttachmentsDirectoryNotWritable,
AttachmentsFileNotDirectory,
AttachmentsNoStorage,
- AutoRefreshOn,
- AutoRefreshOff,
AvailableTemplates,
Back,
CloseRequestHeader,
diff --git a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryCalenderView.fxml b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryCalenderView.fxml
index 022563b632..5ebef9e29d 100644
--- a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryCalenderView.fxml
+++ b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryCalenderView.fxml
@@ -3,44 +3,54 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryTableView.fxml b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryTableView.fxml
index 35c827d0d4..77cb41cf45 100644
--- a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryTableView.fxml
+++ b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryTableView.fxml
@@ -4,13 +4,14 @@
+
-
+
@@ -39,21 +40,14 @@
-
-
-
-
-
-
-
-
+
@@ -91,14 +85,22 @@
-
+
+
+
+
+
-
+
diff --git a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/messages.properties b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/messages.properties
index 7573638fcc..00cbf5904f 100644
--- a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/messages.properties
+++ b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/messages.properties
@@ -16,8 +16,6 @@ AttachmentsDirectoryFailedCreate=Failed to create directory {0} for attachments
AttachmentsFileNotDirectory=File {0} exists but is not a directory
AttachmentsSearchProperty=Attachments:
Author=Author:
-AutoRefreshOn=Auto Refresh: ON
-AutoRefreshOff=Auto Refresh: OFF
AvailableTemplates=Available Templates
Back=Back
BrowseButton=Browse
@@ -63,6 +61,7 @@ Level=Level
Logbook=Logbook
Logbooks=Logbooks:
LogbookNotSupported=No Logbook Support
+LogbookServiceUnavailable=Logbook Service Unavailable
LogbookServiceUnavailableTitle=Cannot create logbook entry
LogbookServiceHasNoLogbooks=Logbook service "{0}" has no logbooks or is not available.
LogbooksSearchFailTitle=Logbook Search Error
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
index aca659d8f8..6512c39a79 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -102,15 +102,23 @@ public void removeWebSocketMessageHandler(WebSocketMessageHandler webSocketMessa
/**
* For debugging purposes: peer should just echo back the message on the subscribed topic.
- * @param message Message to echo
+ * @param message Message for the service to echo
*/
@SuppressWarnings("unused")
public void sendEcho(String message) {
- stompSession.send(contextPath + "/web-socket/echo", message);
+ if(stompSession != null && stompSession.isConnected()){
+ stompSession.send(contextPath + "/web-socket/echo", message);
+ }
+
}
+ /**
+ * Disconnects the STOMP session if non-null and connected.
+ */
public void disconnect() {
- stompSession.disconnect();
+ if(stompSession != null && stompSession.isConnected()) {
+ stompSession.disconnect();
+ }
}
/**
From 181775155eb8f42e3b436fac8b0eeb445d9bde01 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Thu, 4 Sep 2025 10:34:40 +0200
Subject: [PATCH 13/23] Cleanup based on review feed-back
---
.../olog/ui/LogEntryTableViewController.java | 1 -
.../ui/write/LogEntryEditorController.java | 39 -------------------
.../WebSocketClientService.java | 3 +-
3 files changed, 2 insertions(+), 41 deletions(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
index 10588f44a2..e97ee7c707 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
@@ -413,7 +413,6 @@ public void search() {
},
(msg, ex) -> {
searchInProgress.set(false);
- //ExceptionDetailsErrorDialog.openError(Messages.LogbooksSearchFailTitle, ex.getMessage(), null);
});
}
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java
index 9302de31b2..2a87b86cec 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java
@@ -555,9 +555,6 @@ public LogTemplate fromString(String name) {
getServerSideStaticData();
setupTextAreaContextMenu();
-
- //doStompStuff();
-
}
/**
@@ -1080,40 +1077,4 @@ private void loadTemplate(LogTemplate logTemplate) {
selectedLogbooks.forEach(l -> updateDropDown(logbookDropDown, l, false));
}
}
-
- private void doStompStuff(){
- WebSocketClient webSocketClient = new StandardWebSocketClient();
- WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
- stompClient.setMessageConverter(new StringMessageConverter());
- String url = "ws://localhost:8080/web-socket";
- StompSessionHandler sessionHandler = new MyStompSessionHandler();
- try {
- StompSession stompSession = stompClient.connect(url, sessionHandler).get();
- stompSession.subscribe("/messages", new StompFrameHandler() {
- @Override
- public Type getPayloadType(StompHeaders headers) {
- return String.class;
- }
-
- @Override
- public void handleFrame(StompHeaders headers, Object payload) {
- System.out.println(payload);
- }
- });
- StompSession.Receiptable receiptable = stompSession.send("/websocket/echo", "Hello World");
- receiptable.getReceiptId();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } catch (ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
-
- private static class MyStompSessionHandler extends StompSessionHandlerAdapter {
-
- @Override
- public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
- System.out.println();
- }
- }
}
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
index 6512c39a79..46d4a58678 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -146,7 +146,8 @@ public void connect(String baseUrl) {
}
/**
- *
+ * Attempts to connect to the remote peer, both in initial connection and in a reconnection scenario.
+ * If connection fails, new attempts are made every 10s until successful.
*/
private void doConnect() {
attemptReconnect.set(true);
From 701d7e55c103a73cff7bc12481a776a7528efda1 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Mon, 8 Sep 2025 10:54:06 +0200
Subject: [PATCH 14/23] Add shutdown of Olog client to abort reconnect thread
---
.../phoebus/logbook/olog/ui/LogbookSearchController.java | 4 ++--
.../websocket/springframework/WebSocketClientService.java | 8 ++++++++
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
index 408d136f8f..eec88b8c84 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
@@ -87,8 +87,8 @@ public void search(Map searchParams, final Consumer
Date: Tue, 23 Sep 2025 10:52:44 +0200
Subject: [PATCH 15/23] Add service API backwards compatibility for Olog client
---
.../phoebus/olog/es/api/OlogHttpClient.java | 2 +-
.../olog/ui/AdvancedSearchViewController.java | 26 ++--
.../logbook/olog/ui/LogEntryCalenderApp.java | 4 +-
.../ui/LogEntryCalenderViewController.java | 11 +-
.../olog/ui/LogEntryTableViewController.java | 19 ++-
.../olog/ui/LogbookSearchController.java | 143 ++++++++++++++----
.../logbook/olog/ui/LogEntryCalenderView.fxml | 2 +-
.../logbook/olog/ui/LogEntryTableView.fxml | 2 +-
.../WebSocketClientService.java | 33 +++-
9 files changed, 184 insertions(+), 58 deletions(-)
diff --git a/app/logbook/olog/client-es/src/main/java/org/phoebus/olog/es/api/OlogHttpClient.java b/app/logbook/olog/client-es/src/main/java/org/phoebus/olog/es/api/OlogHttpClient.java
index 5a82c242e0..5ecb15aeae 100644
--- a/app/logbook/olog/client-es/src/main/java/org/phoebus/olog/es/api/OlogHttpClient.java
+++ b/app/logbook/olog/client-es/src/main/java/org/phoebus/olog/es/api/OlogHttpClient.java
@@ -568,7 +568,7 @@ public Collection listLevels(){
response.body(), new TypeReference>() {
});
} catch (Exception e) {
- LOGGER.log(Level.WARNING, "Unable to get templates from service", e);
+ LOGGER.log(Level.WARNING, "Unable to get levels from service", e);
return Collections.emptySet();
}
}
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java
index 7c1eef8d56..b2b5d2f144 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java
@@ -44,6 +44,7 @@
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
+import org.phoebus.framework.jobs.JobManager;
import org.phoebus.logbook.LogClient;
import org.phoebus.logbook.LogEntryLevel;
import org.phoebus.logbook.Logbook;
@@ -317,18 +318,21 @@ public void initialize() {
}
});
- levelsList.addAll(logClient.listLevels().stream().map(LogEntryLevel::name).sorted().toList());
- levelsList.forEach(level -> {
- LevelSelection levelSelection = new LevelSelection(level, false);
- levelSelections.add(levelSelection);
- CheckBox checkBox = new CheckBox(level);
- LevelSelectionMenuItem levelSelectionMenuItem = new LevelSelectionMenuItem(checkBox);
- levelSelectionMenuItem.setHideOnClick(false);
- checkBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
- levelSelection.selected = newValue;
- setSelectedLevelsString();
+ // Fetch levels from service on separate thread
+ JobManager.schedule("Get logbook levels", monitor -> {
+ levelsList.addAll(logClient.listLevels().stream().map(LogEntryLevel::name).sorted().toList());
+ levelsList.forEach(level -> {
+ LevelSelection levelSelection = new LevelSelection(level, false);
+ levelSelections.add(levelSelection);
+ CheckBox checkBox = new CheckBox(level);
+ LevelSelectionMenuItem levelSelectionMenuItem = new LevelSelectionMenuItem(checkBox);
+ levelSelectionMenuItem.setHideOnClick(false);
+ checkBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
+ levelSelection.selected = newValue;
+ setSelectedLevelsString();
+ });
+ levelsContextMenu.getItems().add(levelSelectionMenuItem);
});
- levelsContextMenu.getItems().add(levelSelectionMenuItem);
});
sortOrderProperty.addListener(searchOnSortChange);
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java
index 3eb48128d3..a5a7e651c2 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java
@@ -17,8 +17,8 @@ public class LogEntryCalenderApp implements AppResourceDescriptor {
public static final Logger logger = Logger.getLogger(LogEntryCalenderApp.class.getName());
static final Image icon = ImageCache.getImage(LogEntryCalenderApp.class, "/icons/logbook-16.png");
- public static final String NAME = "logEntryCalender";
- public static String DISPLAYNAME = "Log Entry Calender";
+ public static final String NAME = "logEntryCalendar";
+ public static String DISPLAYNAME = "Log Entry Calendar";
private static final String SUPPORTED_SCHEMA = "logCalender";
private LogFactory logFactory;
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
index 7d3e96b697..665b7a1cf0 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
@@ -97,9 +97,7 @@ public LogEntryCalenderViewController(LogClient logClient, OlogQueryManager olog
}
@FXML
- @Override
public void initialize() {
- super.initialize();
advancedSearchViewController.setSearchCallback(this::search);
configureComboBox();
@@ -196,9 +194,12 @@ public Void call(Appointment appointment) {
search.disableProperty().bind(searchInProgress);
- connectWebSocket();
-
- search();
+ determineConnectivity(() -> {
+ switch (connectivityModeObjectProperty.get()){
+ case HTTP_ONLY -> search();
+ case WEB_SOCKETS_SUPPORTED -> connectWebSocket();
+ }
+ });
}
// Keeps track of when the animation is active. Multiple clicks will be ignored
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
index 67c8b7d1bb..25f0e531de 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
@@ -86,7 +86,7 @@ public class LogEntryTableViewController extends LogbookSearchController impleme
// elements associated with the various search
@FXML
@SuppressWarnings("unused")
- private GridPane ViewSearchPane;
+ private GridPane viewSearchPane;
// elements related to the table view of the log entries
@FXML
@@ -167,9 +167,7 @@ protected void setGoBackAndGoForwardActions(LogEntryTable.GoBackAndGoForwardActi
protected Optional goBackAndGoForwardActions = Optional.empty();
@FXML
- @Override
public void initialize() {
- super.initialize();
logEntryDisplayController.setLogEntryTableViewController(this);
@@ -183,7 +181,6 @@ public void initialize() {
});
tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
- tableView.visibleProperty().bind(serviceConnected);
MenuItem groupSelectedEntries = new MenuItem(Messages.GroupSelectedEntries);
groupSelectedEntries.setOnAction(e -> createLogEntryGroup());
@@ -335,9 +332,13 @@ public void updateItem(TableViewListItem logEntry, boolean empty) {
Messages.AdvancedSearchHide : Messages.AdvancedSearchOpen,
advancedSearchVisible));
- logDetailView.disableProperty().bind(serviceConnected.not());
+ determineConnectivity(() -> {
+ switch (connectivityModeObjectProperty.get()){
+ case HTTP_ONLY -> search();
+ case WEB_SOCKETS_SUPPORTED -> connectWebSocket();
+ }
+ });
- connectWebSocket();
}
// Keeps track of when the animation is active. Multiple clicks will be ignored
@@ -361,7 +362,7 @@ public void resize() {
});
} else {
searchParameters.setQuery(query.getEditor().getText());
- double width = ViewSearchPane.getWidth() / 2.5;
+ double width = viewSearchPane.getWidth() / 2.5;
KeyValue kv = new KeyValue(advancedSearchViewController.getPane().minWidthProperty(), width);
KeyValue kv2 = new KeyValue(advancedSearchViewController.getPane().prefWidthProperty(), width);
timeline = new Timeline(new KeyFrame(cycleDuration, kv, kv2));
@@ -406,6 +407,10 @@ public void search() {
searchInProgress.set(false);
setSearchResult(searchResult1);
List queries = ologQueryManager.getQueries();
+ if(connectivityModeObjectProperty.get().equals(ConnectivityMode.HTTP_ONLY)){
+ logger.log(Level.INFO, "Starting periodic search: " + queryString);
+ periodicSearch(params, this::setSearchResult);
+ }
Platform.runLater(() -> {
ologQueries.setAll(queries);
query.getSelectionModel().select(ologQueries.get(0));
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
index eec88b8c84..78cf52bf42 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
@@ -2,13 +2,17 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+import javafx.application.Platform;
+import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import org.phoebus.core.websocket.WebSocketMessageHandler;
import org.phoebus.core.websocket.springframework.WebSocketClientService;
import org.phoebus.framework.jobs.Job;
+import org.phoebus.framework.jobs.JobManager;
import org.phoebus.logbook.LogClient;
import org.phoebus.logbook.LogEntry;
import org.phoebus.logbook.SearchResult;
@@ -19,6 +23,11 @@
import java.net.URI;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.logging.Level;
@@ -39,9 +48,17 @@ public abstract class LogbookSearchController implements WebSocketMessageHandler
private final ObjectMapper objectMapper = new ObjectMapper();
private final Logger logger = Logger.getLogger(LogbookSearchController.class.getName());
- protected final SimpleBooleanProperty serviceConnected = new SimpleBooleanProperty();
+ protected final SimpleBooleanProperty webSocketConnected = new SimpleBooleanProperty();
+ private static final int SEARCH_JOB_INTERVAL = 30; // 30 seconds
+ private ScheduledFuture> runningTask;
+ private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+ private Job logbookSearchJob;
+ protected final ObjectProperty connectivityModeObjectProperty =
+ new SimpleObjectProperty<>(ConnectivityMode.NOT_CONNECTED);
protected WebSocketClientService webSocketClientService;
+ private final String webSocketUrl;
+ private final CountDownLatch connectivityCheckerCountDownLatch = new CountDownLatch(1);
@SuppressWarnings("unused")
@FXML
@@ -49,12 +66,44 @@ public abstract class LogbookSearchController implements WebSocketMessageHandler
@SuppressWarnings("unused")
@FXML
- private GridPane ViewSearchPane;
+ private GridPane viewSearchPane;
- @FXML
- public void initialize() {
- errorPane.visibleProperty().bind(serviceConnected.not());
- ViewSearchPane.visibleProperty().bind(serviceConnected);
+ public LogbookSearchController() {
+ String baseUrl = Preferences.olog_url;
+ URI uri = URI.create(baseUrl);
+ String scheme = uri.getScheme();
+ String host = uri.getHost();
+ int port = uri.getPort();
+ String path = uri.getPath();
+ if (path.endsWith("/")) {
+ path = path.substring(0, path.length() - 1);
+ }
+ String webSocketScheme = scheme.toLowerCase().startsWith("https") ? "wss" : "ws";
+ this.webSocketUrl = webSocketScheme + "://" + host + (port > -1 ? (":" + port) : "") + path;
+ }
+
+ protected void determineConnectivity(Runnable completionHandler){
+
+ // Try to determine the connection mode: is the remote service available at all?
+ // If so, does it accept web socket connections?
+ JobManager.schedule("Connection mode probe", monitor -> {
+ String serviceInfo = client.serviceInfo();
+ if (serviceInfo != null && !serviceInfo.isEmpty()) { // service online, check web socket availability
+ if (WebSocketClientService.checkAvailability(this.webSocketUrl)) {
+ connectivityModeObjectProperty.set(ConnectivityMode.WEB_SOCKETS_SUPPORTED);
+ } else {
+ connectivityModeObjectProperty.set(ConnectivityMode.HTTP_ONLY);
+ }
+ }
+ connectivityCheckerCountDownLatch.countDown();
+ if (connectivityModeObjectProperty.get().equals(ConnectivityMode.NOT_CONNECTED)) {
+ Platform.runLater(() -> {
+ errorPane.visibleProperty().set(true);
+ viewSearchPane.visibleProperty().set(false);
+ });
+ }
+ completionHandler.run();
+ });
}
public void setClient(LogClient client) {
@@ -73,12 +122,44 @@ public LogClient getLogClient() {
* @param errorHandler Client side error handler that should notify user.
*/
public void search(Map searchParams, final Consumer resultHandler, final BiConsumer errorHandler) {
+ cancelPeriodSearch();
LogbookSearchJob.submit(this.client,
searchParams,
resultHandler,
errorHandler);
}
+ /**
+ * Starts a search job every {@link #SEARCH_JOB_INTERVAL} seconds. If a search fails (e.g. service off-line or invalid search parameters),
+ * the period search is cancelled. User will need to implicitly start it again through a "manual" search in the UI.
+ *
+ * @param searchParams The search parameters
+ * @param resultHandler Handler taking care of the search result.
+ */
+ public void periodicSearch(Map searchParams, final Consumer resultHandler) {
+ cancelPeriodSearch();
+ runningTask = executor.scheduleAtFixedRate(() -> logbookSearchJob = LogbookSearchJob.submit(this.client,
+ searchParams,
+ resultHandler,
+ (url, ex) -> {
+ searchInProgress.set(false);
+ cancelPeriodSearch();
+ }), SEARCH_JOB_INTERVAL, SEARCH_JOB_INTERVAL, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Stops periodic search and ongoing search jobs, if any.
+ */
+ private void cancelPeriodSearch() {
+ if (runningTask != null) {
+ runningTask.cancel(true);
+ }
+
+ if (logbookSearchJob != null) {
+ logbookSearchJob.cancel();
+ }
+ }
+
@Deprecated
public abstract void setLogs(List logs);
@@ -90,33 +171,35 @@ public void shutdown() {
Logger.getLogger(LogbookSearchController.class.getName()).log(Level.INFO, "Shutting down web socket");
webSocketClientService.shutdown();
}
+ if(connectivityModeObjectProperty.get().equals(ConnectivityMode.HTTP_ONLY)){
+ cancelPeriodSearch();
+ }
}
protected abstract void search();
protected void connectWebSocket() {
- String baseUrl = Preferences.olog_url;
- URI uri = URI.create(baseUrl);
- String scheme = uri.getScheme();
- String host = uri.getHost();
- int port = uri.getPort();
- String path = uri.getPath();
- if (path.endsWith("/")) {
- path = path.substring(0, path.length() - 1);
+ try {
+ if(connectivityCheckerCountDownLatch.await(3000, TimeUnit.MILLISECONDS)){
+ if (connectivityModeObjectProperty.get().equals(ConnectivityMode.WEB_SOCKETS_SUPPORTED)) {
+ webSocketClientService = new WebSocketClientService(() -> {
+ logger.log(Level.INFO, "Connected to web socket on " + webSocketUrl);
+ webSocketConnected.setValue(true);
+ errorPane.visibleProperty().set(false);
+ search();
+ }, () -> {
+ logger.log(Level.INFO, "Disconnected from web socket on " + webSocketUrl);
+ webSocketConnected.set(false);
+ errorPane.visibleProperty().set(true);
+ });
+ webSocketClientService.addWebSocketMessageHandler(this);
+ webSocketClientService.connect(webSocketUrl);
+ }
+ }
+ } catch (InterruptedException e) {
+ Logger.getLogger(LogbookSearchController.class.getName()).log(Level.WARNING, "Timed out waiting for connectivity check");
+ connectivityModeObjectProperty.set(ConnectivityMode.NOT_CONNECTED);
}
- String webSocketScheme = scheme.toLowerCase().startsWith("https") ? "wss" : "ws";
- String webSocketUrl = webSocketScheme + "://" + host + (port > -1 ? (":" + port) : "") + path;
-
- webSocketClientService = new WebSocketClientService(() -> {
- logger.log(Level.INFO, "Connected to web socket on " + webSocketUrl);
- serviceConnected.setValue(true);
- search();
- }, () -> {
- logger.log(Level.INFO, "Disconnected from web socket on " + webSocketUrl);
- serviceConnected.set(false);
- });
- webSocketClientService.addWebSocketMessageHandler(this);
- webSocketClientService.connect(webSocketUrl);
}
@Override
@@ -137,4 +220,10 @@ public void handleWebSocketMessage(String message) {
logger.log(Level.WARNING, "Unable to deserialize message \"" + message + "\"");
}
}
+
+ protected enum ConnectivityMode {
+ NOT_CONNECTED,
+ HTTP_ONLY,
+ WEB_SOCKETS_SUPPORTED
+ }
}
diff --git a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryCalenderView.fxml b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryCalenderView.fxml
index 5ebef9e29d..82209c507c 100644
--- a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryCalenderView.fxml
+++ b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryCalenderView.fxml
@@ -10,7 +10,7 @@
-
+
diff --git a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryTableView.fxml b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryTableView.fxml
index 77cb41cf45..bbc06dac4e 100644
--- a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryTableView.fxml
+++ b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/LogEntryTableView.fxml
@@ -13,7 +13,7 @@
-
+
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
index 8230c6fc46..4cee6aceb6 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -23,6 +23,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -102,11 +103,12 @@ public void removeWebSocketMessageHandler(WebSocketMessageHandler webSocketMessa
/**
* For debugging purposes: peer should just echo back the message on the subscribed topic.
+ *
* @param message Message for the service to echo
*/
@SuppressWarnings("unused")
public void sendEcho(String message) {
- if(stompSession != null && stompSession.isConnected()){
+ if (stompSession != null && stompSession.isConnected()) {
stompSession.send(contextPath + "/web-socket/echo", message);
}
@@ -116,7 +118,7 @@ public void sendEcho(String message) {
* Disconnects the STOMP session if non-null and connected.
*/
public void disconnect() {
- if(stompSession != null && stompSession.isConnected()) {
+ if (stompSession != null && stompSession.isConnected()) {
stompSession.disconnect();
}
}
@@ -124,7 +126,7 @@ public void disconnect() {
/**
* Disconnects the socket if connected and terminates connection thread.
*/
- public void shutdown(){
+ public void shutdown() {
disconnect();
attemptReconnect.set(false);
}
@@ -269,4 +271,29 @@ public void handleTransportError(StompSession session, Throwable exception) {
}
}
}
+
+ /**
+ * Utility method to check availability of a web socket connection. Tries to connect once,
+ * and - if successful - subsequently closes the web socket connection.
+ *
+ * @param webSocketConnectUrl The web socket URL
+ * @return true if connection to web socket succeeds within 3000 ms.
+ */
+ public static boolean checkAvailability(String webSocketConnectUrl) {
+ WebSocketClient webSocketClient = new StandardWebSocketClient();
+ WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
+ try {
+ StompSession stompSession = stompClient.connect(webSocketConnectUrl + "/web-socket", new StompSessionHandlerAdapter() {
+ @Override
+ public Type getPayloadType(StompHeaders headers) {
+ return super.getPayloadType(headers);
+ }
+ }).get(3000, TimeUnit.MILLISECONDS);
+ stompSession.disconnect();
+ return true;
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Remote service on " + webSocketConnectUrl + " does not support web socket connection", e);
+ }
+ return false;
+ }
}
From 565860fb0d4d13fd43d477bc4648d368ac0dbcbc Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Tue, 23 Sep 2025 11:31:52 +0200
Subject: [PATCH 16/23] Moved web socket URLs and endpoints to API client
---
.../olog/ui/LogbookSearchController.java | 25 +++++--
.../WebSocketClientService.java | 72 ++++++++-----------
2 files changed, 48 insertions(+), 49 deletions(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
index 78cf52bf42..22a34779fa 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
@@ -57,7 +57,8 @@ public abstract class LogbookSearchController implements WebSocketMessageHandler
new SimpleObjectProperty<>(ConnectivityMode.NOT_CONNECTED);
protected WebSocketClientService webSocketClientService;
- private final String webSocketUrl;
+ private final String webSocketConnectUrl;
+ private final String subscriptionEndpoint;
private final CountDownLatch connectivityCheckerCountDownLatch = new CountDownLatch(1);
@SuppressWarnings("unused")
@@ -79,9 +80,16 @@ public LogbookSearchController() {
path = path.substring(0, path.length() - 1);
}
String webSocketScheme = scheme.toLowerCase().startsWith("https") ? "wss" : "ws";
- this.webSocketUrl = webSocketScheme + "://" + host + (port > -1 ? (":" + port) : "") + path;
+ this.webSocketConnectUrl = webSocketScheme + "://" + host + (port > -1 ? (":" + port) : "") + path + "/web-socket";
+ this.subscriptionEndpoint = path + "/web-socket/messages";
}
+ /**
+ * Determines how the client may connect to the remote service. The service info endpoint is called to establish
+ * availability of the service. If available, then a single web socket connection is attempted to determine
+ * if the service supports web sockets.
+ * @param completionHandler {@link Runnable} called when connection mode has been determined.
+ */
protected void determineConnectivity(Runnable completionHandler){
// Try to determine the connection mode: is the remote service available at all?
@@ -89,7 +97,7 @@ protected void determineConnectivity(Runnable completionHandler){
JobManager.schedule("Connection mode probe", monitor -> {
String serviceInfo = client.serviceInfo();
if (serviceInfo != null && !serviceInfo.isEmpty()) { // service online, check web socket availability
- if (WebSocketClientService.checkAvailability(this.webSocketUrl)) {
+ if (WebSocketClientService.checkAvailability(this.webSocketConnectUrl)) {
connectivityModeObjectProperty.set(ConnectivityMode.WEB_SOCKETS_SUPPORTED);
} else {
connectivityModeObjectProperty.set(ConnectivityMode.HTTP_ONLY);
@@ -183,17 +191,17 @@ protected void connectWebSocket() {
if(connectivityCheckerCountDownLatch.await(3000, TimeUnit.MILLISECONDS)){
if (connectivityModeObjectProperty.get().equals(ConnectivityMode.WEB_SOCKETS_SUPPORTED)) {
webSocketClientService = new WebSocketClientService(() -> {
- logger.log(Level.INFO, "Connected to web socket on " + webSocketUrl);
+ logger.log(Level.INFO, "Connected to web socket on " + webSocketConnectUrl);
webSocketConnected.setValue(true);
errorPane.visibleProperty().set(false);
search();
}, () -> {
- logger.log(Level.INFO, "Disconnected from web socket on " + webSocketUrl);
+ logger.log(Level.INFO, "Disconnected from web socket on " + webSocketConnectUrl);
webSocketConnected.set(false);
errorPane.visibleProperty().set(true);
- });
+ }, webSocketConnectUrl, subscriptionEndpoint, null);
webSocketClientService.addWebSocketMessageHandler(this);
- webSocketClientService.connect(webSocketUrl);
+ webSocketClientService.connect();
}
}
} catch (InterruptedException e) {
@@ -221,6 +229,9 @@ public void handleWebSocketMessage(String message) {
}
}
+ /**
+ * Enum to indicate if and how the client may connect to remote Olog service.
+ */
protected enum ConnectivityMode {
NOT_CONNECTED,
HTTP_ONLY,
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
index 4cee6aceb6..1d21ee7c93 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -50,19 +50,24 @@
*/
public class WebSocketClientService {
- /**
- * URL to which the client connects
- */
- private String webSocketConnectUrl;
- /**
- * Path string, depends on service deployment context, e.g. Olog
- */
- private String contextPath;
+
private StompSession stompSession;
private Runnable connectCallback;
private Runnable disconnectCallback;
private final List webSocketMessageHandlers = Collections.synchronizedList(new ArrayList<>());
private final AtomicBoolean attemptReconnect = new AtomicBoolean();
+ /**
+ * Full path to the web socket connection URL, e.g. ws://localhost:8080/Olog/web-socket
+ */
+ private String connectUrl;
+ /**
+ * Subscription endpoint, e.g. /Olog/web-socket/messages
+ */
+ private String subscriptionEndpoint;
+ /**
+ * Echo endpoint /Olog/web-socket/echo
+ */
+ private String echoEndpoint;
private static final Logger logger = Logger.getLogger(WebSocketClientService.class.getName());
@@ -70,15 +75,22 @@ public class WebSocketClientService {
* Constructor if connect/disconnect callbacks are not needed.
*/
@SuppressWarnings("unused")
- public WebSocketClientService() {
+ public WebSocketClientService(String connectUrl, String subscriptionEndpoint, String echoEndpoint) {
+ this.connectUrl = connectUrl;
+ this.subscriptionEndpoint = subscriptionEndpoint;
+ this.echoEndpoint = echoEndpoint;
}
/**
* @param connectCallback The non-null method called when connection to the remote web socket has been successfully established.
* @param disconnectCallback The non-null method called when connection to the remote web socket has been lost, e.g.
* remote peer has been shut down.
+ * @param connectUrl URL to the service web socket, e.g. ws://localhost:8080/Olog/web.socket
+ * @param subscriptionEndpoint E.g. /Olog/web-socket/messages
+ * @param echoEndpoint E.g. /Olog/web-socket/echo. May be null if client has no need for echo messages.
*/
- public WebSocketClientService(Runnable connectCallback, Runnable disconnectCallback) {
+ public WebSocketClientService(Runnable connectCallback, Runnable disconnectCallback, String connectUrl, String subscriptionEndpoint, String echoEndpoint) {
+ this(connectUrl, subscriptionEndpoint, echoEndpoint);
this.connectCallback = connectCallback;
this.disconnectCallback = disconnectCallback;
}
@@ -108,8 +120,8 @@ public void removeWebSocketMessageHandler(WebSocketMessageHandler webSocketMessa
*/
@SuppressWarnings("unused")
public void sendEcho(String message) {
- if (stompSession != null && stompSession.isConnected()) {
- stompSession.send(contextPath + "/web-socket/echo", message);
+ if (stompSession != null && stompSession.isConnected() && echoEndpoint != null) {
+ stompSession.send(echoEndpoint, message);
}
}
@@ -131,35 +143,11 @@ public void shutdown() {
attemptReconnect.set(false);
}
- /**
- * Attempts to connect to remote web socket.
- *
- * @param baseUrl The "base" URL of the web socket peer, must start with ws:// or wss://. Note that "web-socket" will be
- * appended to this URL. Further, the URL may contain a path, e.g. ws://host:port/path.
- * @throws IllegalArgumentException if baseUrl is null, empty or does not start with ws:// or wss://.
- */
- public void connect(String baseUrl) {
- if (baseUrl == null || baseUrl.isEmpty() || (!baseUrl.toLowerCase().startsWith("ws://") && !baseUrl.toLowerCase().startsWith("wss://"))) {
- throw new IllegalArgumentException("URL \"" + baseUrl + "\" is not valid");
- }
- URI uri = URI.create(baseUrl);
- String scheme = uri.getScheme();
- String host = uri.getHost();
- int port = uri.getPort();
- String path = uri.getPath();
- if (path.endsWith("/")) {
- path = path.substring(0, path.length() - 1);
- }
- this.contextPath = path;
- this.webSocketConnectUrl = scheme + "://" + host + (port > -1 ? (":" + port) : "") + this.contextPath + "/web-socket";
- doConnect();
- }
-
/**
* Attempts to connect to the remote peer, both in initial connection and in a reconnection scenario.
* If connection fails, new attempts are made every 10s until successful.
*/
- private void doConnect() {
+ public void connect() {
attemptReconnect.set(true);
WebSocketClient webSocketClient = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
@@ -171,10 +159,10 @@ private void doConnect() {
StompSessionHandler sessionHandler = new StompSessionHandler();
new Thread(() -> {
while (attemptReconnect.get()) {
- logger.log(Level.INFO, "Attempting web socket connection to " + webSocketConnectUrl);
+ logger.log(Level.INFO, "Attempting web socket connection to " + connectUrl);
try {
- stompSession = stompClient.connect(this.webSocketConnectUrl, sessionHandler).get();
- stompSession.subscribe(contextPath + "/web-socket/messages", new StompFrameHandler() {
+ stompSession = stompClient.connect(connectUrl, sessionHandler).get();
+ stompSession.subscribe(this.subscriptionEndpoint, new StompFrameHandler() {
@Override
public Type getPayloadType(StompHeaders headers) {
return String.class;
@@ -262,7 +250,7 @@ public void handleException(StompSession session, @Nullable StompCommand command
public void handleTransportError(StompSession session, Throwable exception) {
if (exception instanceof ConnectionLostException) {
logger.log(Level.WARNING, "Connection lost, will attempt to reconnect", exception);
- doConnect();
+ connect();
} else {
logger.log(Level.WARNING, "Handling transport exception", exception);
}
@@ -283,7 +271,7 @@ public static boolean checkAvailability(String webSocketConnectUrl) {
WebSocketClient webSocketClient = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
try {
- StompSession stompSession = stompClient.connect(webSocketConnectUrl + "/web-socket", new StompSessionHandlerAdapter() {
+ StompSession stompSession = stompClient.connect(webSocketConnectUrl, new StompSessionHandlerAdapter() {
@Override
public Type getPayloadType(StompHeaders headers) {
return super.getPayloadType(headers);
From da4ac48971421aeab3a2aea59262b184ba45f25c Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Tue, 23 Sep 2025 12:25:07 +0200
Subject: [PATCH 17/23] Update UI if web socket is closed when service is not
reachable
---
.../org/phoebus/logbook/olog/ui/LogbookSearchController.java | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
index 22a34779fa..8bf2999a4e 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
@@ -172,7 +172,7 @@ private void cancelPeriodSearch() {
public abstract void setLogs(List logs);
/**
- * Utility method to cancel any ongoing periodic search jobs.
+ * Utility method to cancel any ongoing periodic search jobs or close web socket.
*/
public void shutdown() {
if (webSocketClientService != null) {
@@ -193,11 +193,13 @@ protected void connectWebSocket() {
webSocketClientService = new WebSocketClientService(() -> {
logger.log(Level.INFO, "Connected to web socket on " + webSocketConnectUrl);
webSocketConnected.setValue(true);
+ viewSearchPane.visibleProperty().set(true);
errorPane.visibleProperty().set(false);
search();
}, () -> {
logger.log(Level.INFO, "Disconnected from web socket on " + webSocketConnectUrl);
webSocketConnected.set(false);
+ viewSearchPane.visibleProperty().set(false);
errorPane.visibleProperty().set(true);
}, webSocketConnectUrl, subscriptionEndpoint, null);
webSocketClientService.addWebSocketMessageHandler(this);
From 338d8630763bf9a0da343cd282afba91d22cc6b7 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 1 Oct 2025 09:30:55 +0200
Subject: [PATCH 18/23] Remove web socket handler when shutting down Olog ui
---
.../olog/ui/LogbookSearchController.java | 3 ++-
.../springframework/WebSocketClientService.java | 17 ++++++++---------
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
index 8bf2999a4e..bac9f9d208 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
@@ -175,8 +175,9 @@ private void cancelPeriodSearch() {
* Utility method to cancel any ongoing periodic search jobs or close web socket.
*/
public void shutdown() {
- if (webSocketClientService != null) {
+ if (connectivityModeObjectProperty.get().equals(ConnectivityMode.WEB_SOCKETS_SUPPORTED) && webSocketClientService != null) {
Logger.getLogger(LogbookSearchController.class.getName()).log(Level.INFO, "Shutting down web socket");
+ webSocketClientService.removeWebSocketMessageHandler(this);
webSocketClientService.shutdown();
}
if(connectivityModeObjectProperty.get().equals(ConnectivityMode.HTTP_ONLY)){
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
index 1d21ee7c93..1ce591e81c 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -19,7 +19,6 @@
import org.springframework.web.socket.messaging.WebSocketStompClient;
import java.lang.reflect.Type;
-import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -59,15 +58,15 @@ public class WebSocketClientService {
/**
* Full path to the web socket connection URL, e.g. ws://localhost:8080/Olog/web-socket
*/
- private String connectUrl;
+ private final String connectUrl;
/**
* Subscription endpoint, e.g. /Olog/web-socket/messages
*/
- private String subscriptionEndpoint;
+ private final String subscriptionEndpoint;
/**
* Echo endpoint /Olog/web-socket/echo
*/
- private String echoEndpoint;
+ private final String echoEndpoint;
private static final Logger logger = Logger.getLogger(WebSocketClientService.class.getName());
@@ -82,12 +81,12 @@ public WebSocketClientService(String connectUrl, String subscriptionEndpoint, St
}
/**
- * @param connectCallback The non-null method called when connection to the remote web socket has been successfully established.
- * @param disconnectCallback The non-null method called when connection to the remote web socket has been lost, e.g.
- * remote peer has been shut down.
- * @param connectUrl URL to the service web socket, e.g. ws://localhost:8080/Olog/web.socket
+ * @param connectCallback The non-null method called when connection to the remote web socket has been successfully established.
+ * @param disconnectCallback The non-null method called when connection to the remote web socket has been lost, e.g.
+ * remote peer has been shut down.
+ * @param connectUrl URL to the service web socket, e.g. ws://localhost:8080/Olog/web.socket
* @param subscriptionEndpoint E.g. /Olog/web-socket/messages
- * @param echoEndpoint E.g. /Olog/web-socket/echo. May be null if client has no need for echo messages.
+ * @param echoEndpoint E.g. /Olog/web-socket/echo. May be null if client has no need for echo messages.
*/
public WebSocketClientService(Runnable connectCallback, Runnable disconnectCallback, String connectUrl, String subscriptionEndpoint, String echoEndpoint) {
this(connectUrl, subscriptionEndpoint, echoEndpoint);
From 67eff8c49eb67af3b3f89051e55088d310778f45 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 1 Oct 2025 12:42:03 +0200
Subject: [PATCH 19/23] Updates based on peer feedback
---
.../olog/ui/LogEntryCalenderViewController.java | 3 ++-
.../olog/ui/LogEntryTableViewController.java | 5 +++--
.../logbook/olog/ui/LogbookSearchController.java | 15 ++++++++-------
.../springframework/WebSocketClientService.java | 2 +-
4 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
index 665b7a1cf0..0758e7fbcd 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
@@ -194,7 +194,8 @@ public Void call(Appointment appointment) {
search.disableProperty().bind(searchInProgress);
- determineConnectivity(() -> {
+ determineConnectivity(connectivityMode -> {
+ connectivityModeObjectProperty.set(connectivityMode);
switch (connectivityModeObjectProperty.get()){
case HTTP_ONLY -> search();
case WEB_SOCKETS_SUPPORTED -> connectWebSocket();
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
index 25f0e531de..d75bdb876a 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
@@ -332,8 +332,9 @@ public void updateItem(TableViewListItem logEntry, boolean empty) {
Messages.AdvancedSearchHide : Messages.AdvancedSearchOpen,
advancedSearchVisible));
- determineConnectivity(() -> {
- switch (connectivityModeObjectProperty.get()){
+ determineConnectivity(connectivityMode -> {
+ connectivityModeObjectProperty.set(connectivityMode);
+ switch (connectivityMode){
case HTTP_ONLY -> search();
case WEB_SOCKETS_SUPPORTED -> connectWebSocket();
}
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
index bac9f9d208..8ba73c5d47 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
@@ -88,29 +88,30 @@ public LogbookSearchController() {
* Determines how the client may connect to the remote service. The service info endpoint is called to establish
* availability of the service. If available, then a single web socket connection is attempted to determine
* if the service supports web sockets.
- * @param completionHandler {@link Runnable} called when connection mode has been determined.
+ * @param consumer {@link Consumer} called when the connectivity mode has been determined.
*/
- protected void determineConnectivity(Runnable completionHandler){
+ protected void determineConnectivity(Consumer consumer){
// Try to determine the connection mode: is the remote service available at all?
// If so, does it accept web socket connections?
JobManager.schedule("Connection mode probe", monitor -> {
+ ConnectivityMode connectivityMode = ConnectivityMode.NOT_CONNECTED;
String serviceInfo = client.serviceInfo();
if (serviceInfo != null && !serviceInfo.isEmpty()) { // service online, check web socket availability
if (WebSocketClientService.checkAvailability(this.webSocketConnectUrl)) {
- connectivityModeObjectProperty.set(ConnectivityMode.WEB_SOCKETS_SUPPORTED);
+ connectivityMode = ConnectivityMode.WEB_SOCKETS_SUPPORTED;
} else {
- connectivityModeObjectProperty.set(ConnectivityMode.HTTP_ONLY);
+ connectivityMode = ConnectivityMode.HTTP_ONLY;
}
}
connectivityCheckerCountDownLatch.countDown();
- if (connectivityModeObjectProperty.get().equals(ConnectivityMode.NOT_CONNECTED)) {
+ consumer.accept(connectivityMode);
+ if (connectivityMode.equals(ConnectivityMode.NOT_CONNECTED)) {
Platform.runLater(() -> {
errorPane.visibleProperty().set(true);
viewSearchPane.visibleProperty().set(false);
});
}
- completionHandler.run();
});
}
@@ -180,7 +181,7 @@ public void shutdown() {
webSocketClientService.removeWebSocketMessageHandler(this);
webSocketClientService.shutdown();
}
- if(connectivityModeObjectProperty.get().equals(ConnectivityMode.HTTP_ONLY)){
+ else if(connectivityModeObjectProperty.get().equals(ConnectivityMode.HTTP_ONLY)){
cancelPeriodSearch();
}
}
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
index 1ce591e81c..453ac5ae3f 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -138,8 +138,8 @@ public void disconnect() {
* Disconnects the socket if connected and terminates connection thread.
*/
public void shutdown() {
- disconnect();
attemptReconnect.set(false);
+ disconnect();
}
/**
From 20ebf73afd052ece62ad90662a7db25585fafbc9 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Thu, 2 Oct 2025 15:04:34 +0200
Subject: [PATCH 20/23] Shutdown Olog UI checks connectivity in synchronized
manner
---
.../ui/LogEntryCalenderViewController.java | 1 +
.../olog/ui/LogEntryTableViewController.java | 1 +
.../olog/ui/LogbookSearchController.java | 45 +++++++++----------
3 files changed, 22 insertions(+), 25 deletions(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
index 0758e7fbcd..c6fe61f308 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java
@@ -196,6 +196,7 @@ public Void call(Appointment appointment) {
determineConnectivity(connectivityMode -> {
connectivityModeObjectProperty.set(connectivityMode);
+ connectivityCheckerCountDownLatch.countDown();
switch (connectivityModeObjectProperty.get()){
case HTTP_ONLY -> search();
case WEB_SOCKETS_SUPPORTED -> connectWebSocket();
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
index d75bdb876a..facf69a4cf 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java
@@ -334,6 +334,7 @@ public void updateItem(TableViewListItem logEntry, boolean empty) {
determineConnectivity(connectivityMode -> {
connectivityModeObjectProperty.set(connectivityMode);
+ connectivityCheckerCountDownLatch.countDown();
switch (connectivityMode){
case HTTP_ONLY -> search();
case WEB_SOCKETS_SUPPORTED -> connectWebSocket();
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
index 8ba73c5d47..1f5a4ac0ee 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookSearchController.java
@@ -59,7 +59,7 @@ public abstract class LogbookSearchController implements WebSocketMessageHandler
protected WebSocketClientService webSocketClientService;
private final String webSocketConnectUrl;
private final String subscriptionEndpoint;
- private final CountDownLatch connectivityCheckerCountDownLatch = new CountDownLatch(1);
+ protected final CountDownLatch connectivityCheckerCountDownLatch = new CountDownLatch(1);
@SuppressWarnings("unused")
@FXML
@@ -104,7 +104,6 @@ protected void determineConnectivity(Consumer consumer){
connectivityMode = ConnectivityMode.HTTP_ONLY;
}
}
- connectivityCheckerCountDownLatch.countDown();
consumer.accept(connectivityMode);
if (connectivityMode.equals(ConnectivityMode.NOT_CONNECTED)) {
Platform.runLater(() -> {
@@ -176,6 +175,11 @@ private void cancelPeriodSearch() {
* Utility method to cancel any ongoing periodic search jobs or close web socket.
*/
public void shutdown() {
+ try {
+ connectivityCheckerCountDownLatch.await(10000, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Logger.getLogger(LogbookSearchController.class.getName()).log(Level.WARNING, "Got InterruptedException waiting for connectivity mode result");
+ }
if (connectivityModeObjectProperty.get().equals(ConnectivityMode.WEB_SOCKETS_SUPPORTED) && webSocketClientService != null) {
Logger.getLogger(LogbookSearchController.class.getName()).log(Level.INFO, "Shutting down web socket");
webSocketClientService.removeWebSocketMessageHandler(this);
@@ -189,29 +193,20 @@ else if(connectivityModeObjectProperty.get().equals(ConnectivityMode.HTTP_ONLY))
protected abstract void search();
protected void connectWebSocket() {
- try {
- if(connectivityCheckerCountDownLatch.await(3000, TimeUnit.MILLISECONDS)){
- if (connectivityModeObjectProperty.get().equals(ConnectivityMode.WEB_SOCKETS_SUPPORTED)) {
- webSocketClientService = new WebSocketClientService(() -> {
- logger.log(Level.INFO, "Connected to web socket on " + webSocketConnectUrl);
- webSocketConnected.setValue(true);
- viewSearchPane.visibleProperty().set(true);
- errorPane.visibleProperty().set(false);
- search();
- }, () -> {
- logger.log(Level.INFO, "Disconnected from web socket on " + webSocketConnectUrl);
- webSocketConnected.set(false);
- viewSearchPane.visibleProperty().set(false);
- errorPane.visibleProperty().set(true);
- }, webSocketConnectUrl, subscriptionEndpoint, null);
- webSocketClientService.addWebSocketMessageHandler(this);
- webSocketClientService.connect();
- }
- }
- } catch (InterruptedException e) {
- Logger.getLogger(LogbookSearchController.class.getName()).log(Level.WARNING, "Timed out waiting for connectivity check");
- connectivityModeObjectProperty.set(ConnectivityMode.NOT_CONNECTED);
- }
+ webSocketClientService = new WebSocketClientService(() -> {
+ logger.log(Level.INFO, "Connected to web socket on " + webSocketConnectUrl);
+ webSocketConnected.setValue(true);
+ viewSearchPane.visibleProperty().set(true);
+ errorPane.visibleProperty().set(false);
+ search();
+ }, () -> {
+ logger.log(Level.INFO, "Disconnected from web socket on " + webSocketConnectUrl);
+ webSocketConnected.set(false);
+ viewSearchPane.visibleProperty().set(false);
+ errorPane.visibleProperty().set(true);
+ }, webSocketConnectUrl, subscriptionEndpoint, null);
+ webSocketClientService.addWebSocketMessageHandler(this);
+ webSocketClientService.connect();
}
@Override
From b1a471b9b237fa1a50ef4dd399efe58cd8b0d3f8 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Fri, 3 Oct 2025 09:58:51 +0200
Subject: [PATCH 21/23] Make connect and disconnect thread safe
---
.../WebSocketClientService.java | 46 +++++++++----------
1 file changed, 22 insertions(+), 24 deletions(-)
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
index 453ac5ae3f..7ab60ed456 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -46,6 +46,10 @@
*
Publish a topic named /path/web-socket/messages, where path is optional.
*
*
+ *
+ * NOTE: client code must call the {@link #shutdown()} method to not
+ * leave the web socket connection alive.
+ *
*/
public class WebSocketClientService {
@@ -122,26 +126,18 @@ public void sendEcho(String message) {
if (stompSession != null && stompSession.isConnected() && echoEndpoint != null) {
stompSession.send(echoEndpoint, message);
}
-
}
/**
- * Disconnects the STOMP session if non-null and connected.
+ * Disconnects the socket if connected and terminates connection thread.
*/
- public void disconnect() {
+ public synchronized void shutdown() {
+ attemptReconnect.set(false);
if (stompSession != null && stompSession.isConnected()) {
stompSession.disconnect();
}
}
- /**
- * Disconnects the socket if connected and terminates connection thread.
- */
- public void shutdown() {
- attemptReconnect.set(false);
- disconnect();
- }
-
/**
* Attempts to connect to the remote peer, both in initial connection and in a reconnection scenario.
* If connection fails, new attempts are made every 10s until successful.
@@ -156,23 +152,25 @@ public void connect() {
stompClient.setTaskScheduler(threadPoolTaskScheduler);
stompClient.setDefaultHeartbeat(new long[]{30000, 30000});
StompSessionHandler sessionHandler = new StompSessionHandler();
+ logger.log(Level.INFO, "Attempting web socket connection to " + connectUrl);
new Thread(() -> {
while (attemptReconnect.get()) {
- logger.log(Level.INFO, "Attempting web socket connection to " + connectUrl);
try {
- stompSession = stompClient.connect(connectUrl, sessionHandler).get();
- stompSession.subscribe(this.subscriptionEndpoint, new StompFrameHandler() {
- @Override
- public Type getPayloadType(StompHeaders headers) {
- return String.class;
- }
+ synchronized (WebSocketClientService.this) {
+ stompSession = stompClient.connect(connectUrl, sessionHandler).get();
+ stompSession.subscribe(this.subscriptionEndpoint, new StompFrameHandler() {
+ @Override
+ public Type getPayloadType(StompHeaders headers) {
+ return String.class;
+ }
- @Override
- public void handleFrame(StompHeaders headers, Object payload) {
- logger.log(Level.INFO, "Handling subscription frame: " + payload);
- webSocketMessageHandlers.forEach(h -> h.handleWebSocketMessage((String) payload));
- }
- });
+ @Override
+ public void handleFrame(StompHeaders headers, Object payload) {
+ logger.log(Level.INFO, "Handling subscription frame: " + payload);
+ webSocketMessageHandlers.forEach(h -> h.handleWebSocketMessage((String) payload));
+ }
+ });
+ }
} catch (Exception e) {
logger.log(Level.WARNING, "Got exception when trying to connect", e);
}
From ca49f74eeb8fb0738b5f31c3815f29fb6adb4fa6 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Fri, 3 Oct 2025 10:06:17 +0200
Subject: [PATCH 22/23] Update controls from server data off UI thread
---
.../olog/ui/AdvancedSearchViewController.java | 76 ++++++++++---------
1 file changed, 39 insertions(+), 37 deletions(-)
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java
index b2b5d2f144..1cf7da8281 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java
@@ -362,45 +362,47 @@ public AnchorPane getPane() {
* @param queryString Query string containing search terms and values
*/
private void updateControls(String queryString) {
- Map queryStringParameters = LogbookQueryUtil.parseHumanReadableQueryString(queryString);
- queryStringParameters.entrySet().forEach(entry -> {
- Keys keys = Keys.findKey(entry.getKey());
- if (keys != null) {
- if (keys.equals(Keys.LEVEL)) {
- List validatedLevels = getValidatedLevelsSelection(entry.getValue());
- if (validatedLevels.isEmpty()) {
- searchParameters.levelsProperty().setValue(null);
- } else {
- String selectedLevels =
- String.join(",", validatedLevels);
- searchParameters.levelsProperty().setValue(selectedLevels);
+ JobManager.schedule("Update controls from server data", monitor -> {
+ Map queryStringParameters = LogbookQueryUtil.parseHumanReadableQueryString(queryString);
+ queryStringParameters.entrySet().forEach(entry -> {
+ Keys keys = Keys.findKey(entry.getKey());
+ if (keys != null) {
+ if (keys.equals(Keys.LEVEL)) {
+ List validatedLevels = getValidatedLevelsSelection(entry.getValue());
+ if (validatedLevels.isEmpty()) {
+ searchParameters.levelsProperty().setValue(null);
+ } else {
+ String selectedLevels =
+ String.join(",", validatedLevels);
+ searchParameters.levelsProperty().setValue(selectedLevels);
+ }
+ levelsContextMenu.getItems().forEach(mi -> {
+ LevelSelectionMenuItem levelSelectionMenuItem =
+ (LevelSelectionMenuItem) mi;
+ levelSelectionMenuItem.setSelected(validatedLevels.contains(levelSelectionMenuItem.getCheckBox().getText()));
+ });
+ } else if (keys.equals(Keys.LOGBOOKS)) {
+ List validatedLogbookNames = getValidatedLogbooksSelection(entry.getValue());
+ if (validatedLogbookNames.isEmpty()) {
+ searchParameters.logbooksProperty().setValue(null);
+ } else {
+ String selectedLogbooks =
+ String.join(",", validatedLogbookNames);
+ searchParameters.logbooksProperty().setValue(selectedLogbooks);
+ }
+ logbookSearchPopover.setSelected(validatedLogbookNames);
+ } else if (keys.equals(Keys.TAGS)) {
+ List validatedTagsNames = getValidatedTagsSelection(entry.getValue());
+ if (validatedTagsNames.isEmpty()) {
+ searchParameters.tagsProperty().setValue(null);
+ } else {
+ String selectedTags = String.join(",", validatedTagsNames);
+ searchParameters.tagsProperty().setValue(selectedTags);
+ }
+ tagSearchPopover.setSelected(validatedTagsNames);
}
- levelsContextMenu.getItems().forEach(mi -> {
- LevelSelectionMenuItem levelSelectionMenuItem =
- (LevelSelectionMenuItem) mi;
- levelSelectionMenuItem.setSelected(validatedLevels.contains(levelSelectionMenuItem.getCheckBox().getText()));
- });
- } else if (keys.equals(Keys.LOGBOOKS)) {
- List validatedLogbookNames = getValidatedLogbooksSelection(entry.getValue());
- if (validatedLogbookNames.isEmpty()) {
- searchParameters.logbooksProperty().setValue(null);
- } else {
- String selectedLogbooks =
- String.join(",", validatedLogbookNames);
- searchParameters.logbooksProperty().setValue(selectedLogbooks);
- }
- logbookSearchPopover.setSelected(validatedLogbookNames);
- } else if (keys.equals(Keys.TAGS)) {
- List validatedTagsNames = getValidatedTagsSelection(entry.getValue());
- if (validatedTagsNames.isEmpty()) {
- searchParameters.tagsProperty().setValue(null);
- } else {
- String selectedTags = String.join(",", validatedTagsNames);
- searchParameters.tagsProperty().setValue(selectedTags);
- }
- tagSearchPopover.setSelected(validatedTagsNames);
}
- }
+ });
});
}
From fdd3aea38f98cc765aa429031dd0fbbdbdc6bc72 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Fri, 3 Oct 2025 11:48:43 +0200
Subject: [PATCH 23/23] Update connect logic
---
.../WebSocketClientService.java | 33 ++++++++++---------
1 file changed, 18 insertions(+), 15 deletions(-)
diff --git a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
index 7ab60ed456..5b83cb5bb6 100644
--- a/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
+++ b/core/websocket/src/main/java/org/phoebus/core/websocket/springframework/WebSocketClientService.java
@@ -154,22 +154,26 @@ public void connect() {
StompSessionHandler sessionHandler = new StompSessionHandler();
logger.log(Level.INFO, "Attempting web socket connection to " + connectUrl);
new Thread(() -> {
- while (attemptReconnect.get()) {
- try {
+ while (true) {
+ try{
synchronized (WebSocketClientService.this) {
- stompSession = stompClient.connect(connectUrl, sessionHandler).get();
- stompSession.subscribe(this.subscriptionEndpoint, new StompFrameHandler() {
- @Override
- public Type getPayloadType(StompHeaders headers) {
- return String.class;
- }
+ if(attemptReconnect.get()) {
+ stompSession = stompClient.connect(connectUrl, sessionHandler).get();
+ stompSession.subscribe(this.subscriptionEndpoint, new StompFrameHandler() {
+ @Override
+ public Type getPayloadType(StompHeaders headers) {
+ return String.class;
+ }
- @Override
- public void handleFrame(StompHeaders headers, Object payload) {
- logger.log(Level.INFO, "Handling subscription frame: " + payload);
- webSocketMessageHandlers.forEach(h -> h.handleWebSocketMessage((String) payload));
- }
- });
+ @Override
+ public void handleFrame(StompHeaders headers, Object payload) {
+ logger.log(Level.INFO, "Handling subscription frame: " + payload);
+ webSocketMessageHandlers.forEach(h -> h.handleWebSocketMessage((String) payload));
+ }
+ });
+ attemptReconnect.set(false);
+ }
+ break;
}
} catch (Exception e) {
logger.log(Level.WARNING, "Got exception when trying to connect", e);
@@ -210,7 +214,6 @@ public void handleFrame(StompHeaders headers, @Nullable Object payload) {
*/
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
- attemptReconnect.set(false);
logger.log(Level.INFO, "Connected to web socket");
if (connectCallback != null) {
connectCallback.run();