Skip to content

Commit 7a9d940

Browse files
committed
Code cleanup, javadoc and doc
1 parent ea1d316 commit 7a9d940

File tree

8 files changed

+86
-104
lines changed

8 files changed

+86
-104
lines changed

app/save-and-restore/app/doc/index.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,13 @@ A word of caution
5858
-----------------
5959

6060
Save-and-restore data is persisted in a central service and is therefore accessible by multiple
61-
clients. Users should keep in mind that changes (e.g. new or deleted nodes) are not pushed to all clients.
62-
Caution is therefore advocated when working on the nodes in the tree, in particular when changing the structure by
63-
copying, deleting or moving nodes.
61+
clients. Users should keep in mind that changes (e.g. new or deleted nodes) are pushed by the service to
62+
all connected clients. If any other user is working in the save-and-restore app, saved changes may update
63+
the current view. For instance, if a folder node is expanded and another user adds an object (folder
64+
or configuration) to that folder, the new object will automatically be added to the expanded folder.
65+
66+
In other words, changes in the current view are triggered not only by the current user, but may be triggered as a result of
67+
changes done by others.
6468

6569
Tree View Context Menu
6670
----------------------

app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -977,8 +977,7 @@ private void exportToCSV() {
977977
protected void addTagToSnapshots() {
978978
ObservableList<TreeItem<Node>> selectedItems = browserSelectionModel.getSelectedItems();
979979
List<Node> selectedNodes = selectedItems.stream().map(TreeItem::getValue).collect(Collectors.toList());
980-
List<Node> updatedNodes = TagUtil.addTag(selectedNodes);
981-
//updatedNodes.forEach(this::nodeChanged);
980+
TagUtil.addTag(selectedNodes);
982981
}
983982

984983
/**

app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/websocket/MessageType.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44

55
package org.phoebus.applications.saveandrestore.model.websocket;
66

7+
/**
8+
* Enum to indicate what type of web socket message the service is sending to clients.
9+
*/
710
public enum MessageType {
811
NODE_ADDED,
912
NODE_UPDATED,
1013
NODE_REMOVED,
1114
FILTER_ADDED_OR_UPDATED,
12-
FILTER_REMOVED;
15+
FILTER_REMOVED
1316
}

app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/websocket/SaveAndRestoreWebSocketMessage.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
package org.phoebus.applications.saveandrestore.model.websocket;
66

7-
7+
/**
8+
* Record encapsulating a {@link MessageType} and a payload.
9+
* @param messageType The {@link MessageType} of a web socket message
10+
* @param payload The payload, e.g. {@link String} or {@link org.phoebus.applications.saveandrestore.model.Node}
11+
*/
812
public record SaveAndRestoreWebSocketMessage<T>(MessageType messageType, T payload) {
913
}

app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/websocket/WebMessageDeserializer.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,38 +10,43 @@
1010
import com.fasterxml.jackson.databind.JsonNode;
1111
import com.fasterxml.jackson.databind.ObjectMapper;
1212
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
13-
import org.phoebus.applications.saveandrestore.model.Node;
14-
import org.phoebus.applications.saveandrestore.model.search.Filter;
1513

14+
/**
15+
* Custom JSON deserializer of {@link SaveAndRestoreWebSocketMessage}s.
16+
*/
1617
public class WebMessageDeserializer extends StdDeserializer<SaveAndRestoreWebSocketMessage> {
1718

18-
private ObjectMapper objectMapper = new ObjectMapper();
19+
private final ObjectMapper objectMapper = new ObjectMapper();
1920

2021
public WebMessageDeserializer(Class<?> clazz) {
2122
super(clazz);
2223
}
2324

25+
/**
26+
* Deserializes a {@link SaveAndRestoreWebSocketMessage}-
27+
*
28+
* @param jsonParser Parsed used for reading JSON content
29+
* @param context Context that can be used to access information about
30+
* this deserialization activity.
31+
* @return A {@link SaveAndRestoreWebSocketMessage} object, or <code>null</code> if deserialization fails.
32+
*/
2433
@Override
2534
public SaveAndRestoreWebSocketMessage<? extends Object> deserialize(JsonParser jsonParser,
2635
DeserializationContext context) {
2736
try {
2837
JsonNode rootNode = jsonParser.getCodec().readTree(jsonParser);
2938
String messageType = rootNode.get("messageType").asText();
3039
switch (MessageType.valueOf(messageType)) {
31-
case NODE_ADDED, NODE_REMOVED, FILTER_REMOVED-> {
32-
SaveAndRestoreWebSocketMessage<String> saveAndRestoreWebSocketMessage =
33-
objectMapper.readValue(rootNode.toString(), SaveAndRestoreWebSocketMessage.class);
34-
return saveAndRestoreWebSocketMessage;
40+
case NODE_ADDED, NODE_REMOVED, FILTER_REMOVED -> {
41+
return objectMapper.readValue(rootNode.toString(), SaveAndRestoreWebSocketMessage.class);
3542
}
3643
case NODE_UPDATED -> {
37-
SaveAndRestoreWebSocketMessage<Node> saveAndRestoreWebSocketMessage = objectMapper.readValue(rootNode.toString(), new TypeReference<>() {
44+
return objectMapper.readValue(rootNode.toString(), new TypeReference<>() {
3845
});
39-
return saveAndRestoreWebSocketMessage;
4046
}
4147
case FILTER_ADDED_OR_UPDATED -> {
42-
SaveAndRestoreWebSocketMessage<Filter> saveAndRestoreWebSocketMessage = objectMapper.readValue(rootNode.toString(), new TypeReference<>() {
48+
return objectMapper.readValue(rootNode.toString(), new TypeReference<>() {
4349
});
44-
return saveAndRestoreWebSocketMessage;
4550
}
4651
}
4752
} catch (Exception e) {

core/websocket/src/main/java/org/phoebus/core/websocket/WebSocketClient.java

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,26 @@ public class WebSocketClient implements WebSocket.Listener {
2929

3030
/**
3131
* @param uri The URI of the web socket peer.
32+
* @param onTextCallback A callback method the API client will use to process web socket messages.
3233
*/
3334
public WebSocketClient(URI uri, Consumer<CharSequence> onTextCallback) {
3435
this.uri = uri;
3536
this.onTextCallback = onTextCallback;
3637
}
3738

39+
/**
40+
* Attempts to connect to the remote web socket.
41+
*/
3842
public void connect() {
3943
attemptConnect.set(true);
40-
reconnect();
44+
doConnect();
4145
}
4246

43-
private void reconnect() {
47+
/**
48+
* Internal connect implementation. This is done in a loop with 10 s intervals until
49+
* connection is established.
50+
*/
51+
private void doConnect() {
4452
new Thread(() -> {
4553
while (attemptConnect.get()) {
4654
logger.log(Level.INFO, "Attempting web socket connection to " + uri);
@@ -63,6 +71,12 @@ private void reconnect() {
6371
}).start();
6472
}
6573

74+
/**
75+
* Called when connection has been established. An API client may optionally register a
76+
* {@link #connectCallback} which is called when connection is opened.
77+
* @param webSocket
78+
* the WebSocket that has been connected
79+
*/
6680
@Override
6781
public void onOpen(WebSocket webSocket) {
6882
WebSocket.Listener.super.onOpen(webSocket);
@@ -87,6 +101,16 @@ public void sendText(String message) {
87101
}
88102

89103

104+
/**
105+
* Called when connection has been closed, e.g. by remote peer. An API client may optionally register a
106+
* {@link #disconnectCallback} which is called when connection is opened.
107+
*
108+
* <p>
109+
* Note that reconnection will be attempted immediately.
110+
* </p>
111+
* @param webSocket
112+
* the WebSocket that has been connected
113+
*/
90114
@Override
91115
public CompletionStage<?> onClose(WebSocket webSocket,
92116
int statusCode,
@@ -95,7 +119,7 @@ public CompletionStage<?> onClose(WebSocket webSocket,
95119
if (disconnectCallback != null) {
96120
disconnectCallback.run();
97121
}
98-
reconnect();
122+
doConnect();
99123
return null;
100124
}
101125

@@ -131,15 +155,34 @@ public CompletionStage<?> onText(WebSocket webSocket,
131155
return WebSocket.Listener.super.onText(webSocket, data, last);
132156
}
133157

158+
/**
159+
*
160+
* <b>NOTE:</b> this <b>must</b> be called by the API client when web socket messages are no longer
161+
* needed, otherwise reconnect attempts will continue as these run on a separate thread.
162+
*
163+
* <p>
164+
* The status code 1000 is used when calling the {@link WebSocket#sendClose(int, String)} method. See
165+
* list of common web socket status codes
166+
* <a href='https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code'>here</a>.
167+
* </p>
168+
* @param reason Custom reason text.
169+
*/
134170
public void close(String reason) {
135171
attemptConnect.set(false);
136172
webSocket.sendClose(1000, reason);
137173
}
138174

175+
/**
176+
* @param connectCallback A {@link Runnable} invoked when web socket connects successfully.
177+
*/
139178
public void setConnectCallback(Runnable connectCallback) {
140179
this.connectCallback = connectCallback;
141180
}
142181

182+
/**
183+
* @param disconnectCallback A {@link Runnable} invoked when web socket disconnects, either
184+
* when closed explicitly, or if remote peer goes away.
185+
*/
143186
public void setDisconnectCallback(Runnable disconnectCallback) {
144187
this.disconnectCallback = disconnectCallback;
145188
}

services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/websocket/WebSocket.java

Lines changed: 7 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,20 @@
88

99
import com.fasterxml.jackson.databind.JsonNode;
1010
import com.fasterxml.jackson.databind.ObjectMapper;
11-
import com.fasterxml.jackson.databind.node.JsonNodeType;
12-
import org.epics.vtype.VType;
1311
import org.springframework.web.socket.TextMessage;
1412
import org.springframework.web.socket.WebSocketSession;
1513

16-
import java.util.ArrayList;
17-
import java.util.Iterator;
18-
import java.util.List;
1914
import java.util.concurrent.ArrayBlockingQueue;
2015
import java.util.concurrent.atomic.AtomicBoolean;
2116
import java.util.logging.Level;
2217
import java.util.logging.Logger;
2318

19+
/**
20+
* Utility class for handling web socket messages. In the context of the save-and-restore service,
21+
* only messages from server are expected. Client messages are logged, but do not invoke any behavior.
22+
*/
2423
@SuppressWarnings("nls")
2524
public class WebSocket {
26-
/**
27-
* Time when web socket was created
28-
*/
29-
private final long created = System.currentTimeMillis();
30-
31-
/**
32-
* Track when the last message was received by web client
33-
*/
34-
private volatile long lastClientMessage = 0;
35-
36-
/**
37-
* Track when the last message was sent to web client
38-
*/
39-
private volatile long lastMessageSent = 0;
4025

4126
/**
4227
* Is the queue full?
@@ -54,9 +39,8 @@ public class WebSocket {
5439

5540
private static final String EXIT_MESSAGE = "EXIT";
5641

57-
private volatile WebSocketSession session;
58-
private volatile String id;
59-
42+
private final WebSocketSession session;
43+
private final String id;
6044

6145
private final Logger logger = Logger.getLogger(WebSocket.class.getName());
6246

@@ -74,7 +58,6 @@ public WebSocket(ObjectMapper objectMapper, WebSocketSession webSocketSession) {
7458
writeThread.setName("Web Socket Write Thread " + this.id);
7559
writeThread.setDaemon(true);
7660
writeThread.start();
77-
trackClientUpdate();
7861
}
7962

8063
/**
@@ -87,34 +70,6 @@ public String getId() {
8770
return id;
8871
}
8972

90-
/**
91-
* @return Timestamp (ms since epoch) when socket was created
92-
*/
93-
public long getCreateTime() {
94-
return created;
95-
}
96-
97-
/**
98-
* @return Timestamp (ms since epoch) of last client message
99-
*/
100-
public long getLastClientMessage() {
101-
return lastClientMessage;
102-
}
103-
104-
/**
105-
* @return Timestamp (ms since epoch) of last message sent to client
106-
*/
107-
public long getLastMessageSent() {
108-
return lastMessageSent;
109-
}
110-
111-
/**
112-
* @return Number of queued messages
113-
*/
114-
public int getQueuedMessageCount() {
115-
return writeQueue.size();
116-
}
117-
11873
/**
11974
* @param message Potentially long message
12075
* @return Message shorted to 200 chars
@@ -162,7 +117,6 @@ private void writeQueuedMessages() {
162117
if (!safeSession.isOpen())
163118
throw new Exception("Session closed");
164119
safeSession.sendMessage(new TextMessage(message));
165-
lastMessageSent = System.currentTimeMillis();
166120
} catch (final Exception ex) {
167121
logger.log(Level.WARNING, ex, () -> "Cannot write '" + shorten(message) + "' for " + id);
168122

@@ -182,10 +136,6 @@ private void writeQueuedMessages() {
182136
}
183137
}
184138

185-
public void trackClientUpdate() {
186-
lastClientMessage = System.currentTimeMillis();
187-
}
188-
189139
/**
190140
* Called when client sends a general message
191141
*
@@ -197,12 +147,7 @@ public void handleTextMessage(TextMessage message) throws Exception {
197147
if (node.isMissingNode())
198148
throw new Exception("Missing 'type' in " + shorten(message.getPayload()));
199149
final String type = node.asText();
200-
201-
switch (type) {
202-
case "monitor":
203-
default:
204-
throw new Exception("Unknown message type: " + shorten(message.getPayload()));
205-
}
150+
logger.log(Level.INFO, "Client message type: " + type);
206151
}
207152

208153
/**
@@ -224,24 +169,5 @@ public void dispose() {
224169
logger.log(Level.WARNING, "Error disposing " + getId(), ex);
225170
}
226171
logger.log(Level.INFO, () -> "Web socket " + session.getId() + " closed");
227-
lastClientMessage = 0;
228-
}
229-
230-
231-
private void write(String message, JsonNode json) throws Exception {
232-
JsonNode n = json.path("pv");
233-
if (n.isMissingNode())
234-
throw new Exception("Missing 'pv' in " + shorten(message));
235-
final String pv_name = n.asText();
236-
237-
n = json.path("value");
238-
if (n.isMissingNode())
239-
throw new Exception("Missing 'value' in " + shorten(message));
240-
final Object value;
241-
if (n.getNodeType() == JsonNodeType.NUMBER)
242-
value = n.asDouble();
243-
else
244-
value = n.asText();
245-
246172
}
247173
}

services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/websocket/WebSocketHandler.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,6 @@ protected void handlePongMessage(@NonNull WebSocketSession session, @NonNull Pon
145145
if (webSocketOptional.isEmpty()) {
146146
return; // Should only happen in case of timing issues?
147147
}
148-
webSocketOptional.get().trackClientUpdate();
149-
150148
}
151149

152150
/**

0 commit comments

Comments
 (0)