Skip to content

Commit 4009a09

Browse files
authored
Merge pull request #5 from synyx/better-error-handling
feat!: improve error handling
2 parents c1f52c6 + 08a13de commit 4009a09

10 files changed

+642
-358
lines changed
Lines changed: 100 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,160 +1,110 @@
11
package org.synyx.matrix.bot;
22

3-
import com.fasterxml.jackson.databind.DeserializationFeature;
4-
import com.fasterxml.jackson.databind.ObjectMapper;
5-
import com.fasterxml.jackson.databind.SerializationFeature;
6-
import com.fasterxml.jackson.databind.json.JsonMapper;
7-
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
8-
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
9-
import lombok.extern.slf4j.Slf4j;
103
import org.synyx.matrix.bot.domain.MatrixEventId;
114
import org.synyx.matrix.bot.domain.MatrixRoomId;
12-
import org.synyx.matrix.bot.domain.MatrixUserId;
13-
import org.synyx.matrix.bot.internal.MatrixAuthentication;
14-
import org.synyx.matrix.bot.internal.MatrixEventNotifier;
15-
import org.synyx.matrix.bot.internal.MatrixStateSynchronizer;
16-
import org.synyx.matrix.bot.internal.api.MatrixApi;
17-
import org.synyx.matrix.bot.internal.api.dto.MessageDto;
18-
import org.synyx.matrix.bot.internal.api.dto.ReactionDto;
19-
import org.synyx.matrix.bot.internal.api.dto.ReactionRelatesToDto;
5+
import org.synyx.matrix.bot.internal.MatrixClientImpl;
206

217
import java.util.Optional;
228

23-
@Slf4j
24-
public class MatrixClient {
25-
26-
private final MatrixAuthentication authentication;
27-
private final ObjectMapper objectMapper;
28-
private final MatrixApi api;
29-
private MatrixState state;
30-
private MatrixStateSynchronizer stateSynchronizer;
31-
private MatrixPersistedState persistedState;
32-
private MatrixEventNotifier eventNotifier;
33-
private boolean interruptionRequested;
34-
35-
public MatrixClient(String hostname, String username, String password) {
36-
37-
this.authentication = new MatrixAuthentication(username, password);
38-
this.objectMapper = JsonMapper.builder()
39-
.addModule(new Jdk8Module())
40-
.addModule(new JavaTimeModule())
41-
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
42-
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
43-
.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, true)
44-
.build();
45-
this.api = new MatrixApi(hostname, authentication, objectMapper);
46-
this.state = null;
47-
this.eventNotifier = null;
48-
this.interruptionRequested = false;
9+
/**
10+
* An interface for a client connecting to a matrix server.
11+
* Serves as the main method of communicating with the server.
12+
*/
13+
public interface MatrixClient {
14+
15+
/**
16+
* Creates a new matrix client to connect to the specified server.
17+
*
18+
* @param url The url for connecting to the intended matrix server. Must start with http:// or https://
19+
* @param username The username for logging into the matrix server.
20+
* @param password The password for logging into the matrix server.
21+
* @return A {@link MatrixClient} implementation that connects to the specified matrix server.
22+
*/
23+
static MatrixClient create(String url, String username, String password) {
24+
25+
return new MatrixClientImpl(url, username, password);
4926
}
5027

51-
public void setEventCallback(MatrixEventConsumer eventConsumer) {
52-
53-
this.eventNotifier = MatrixEventNotifier.from(objectMapper, eventConsumer).orElse(null);
54-
}
55-
56-
public void setPersistedState(MatrixPersistedState persistedState) {
57-
58-
this.persistedState = persistedState;
59-
}
60-
61-
public void requestStopOfSync() {
62-
63-
interruptionRequested = true;
64-
api.terminateOpenConnections();
65-
}
66-
67-
public void syncContinuous() {
68-
69-
if (!authentication.isAuthenticated()) {
70-
if (api.login()) {
71-
log.info("Successfully logged in to matrix server as {}",
72-
authentication.getUserId()
73-
.map(MatrixUserId::toString)
74-
.orElse("UNKNOWN")
75-
);
76-
} else {
77-
return;
78-
}
79-
}
80-
81-
state = new MatrixState(authentication.getUserId().orElseThrow(IllegalStateException::new));
82-
stateSynchronizer = new MatrixStateSynchronizer(state, objectMapper);
83-
84-
var maybeSyncResponse = api.syncFull();
85-
String lastBatch;
86-
if (maybeSyncResponse.isPresent()) {
87-
final var syncResponse = maybeSyncResponse.get();
88-
lastBatch = syncResponse.nextBatch();
89-
90-
stateSynchronizer.synchronizeState(syncResponse);
91-
} else {
92-
log.error("Failed to perform initial sync");
93-
return;
94-
}
95-
96-
if (eventNotifier != null) {
97-
eventNotifier.getConsumer().onConnected(state);
98-
}
99-
100-
if (persistedState != null) {
101-
final var maybePersistedLastBatch = persistedState.getLastBatch();
102-
if (maybePersistedLastBatch.isPresent()) {
103-
lastBatch = maybePersistedLastBatch.get();
104-
} else {
105-
persistedState.setLastBatch(lastBatch);
106-
}
107-
}
108-
109-
while (!interruptionRequested) {
110-
maybeSyncResponse = api.sync(lastBatch);
111-
if (maybeSyncResponse.isPresent()) {
112-
final var syncResponse = maybeSyncResponse.get();
113-
lastBatch = syncResponse.nextBatch();
114-
115-
stateSynchronizer.synchronizeState(syncResponse);
116-
117-
if (eventNotifier != null) {
118-
eventNotifier.notifyFromSynchronizationResponse(state, syncResponse);
119-
}
120-
121-
if (persistedState != null) {
122-
persistedState.setLastBatch(lastBatch);
123-
}
124-
}
125-
}
126-
127-
interruptionRequested = false;
128-
}
129-
130-
public boolean isConnected() {
131-
132-
return state != null;
133-
}
134-
135-
public Optional<MatrixState> getState() {
136-
137-
return Optional.ofNullable(state);
138-
}
139-
140-
public boolean sendMessage(MatrixRoomId roomId, String messageBody) {
141-
142-
return api.sendEvent(roomId.getFormatted(), "m.room.message", new MessageDto(messageBody, "m.text"));
143-
}
144-
145-
public boolean addReaction(MatrixRoomId roomId, MatrixEventId eventId, String reaction) {
146-
147-
final var reactionDto = new ReactionDto(new ReactionRelatesToDto(eventId.getFormatted(), reaction));
148-
return api.sendEvent(roomId.getFormatted(), "m.reaction", reactionDto);
149-
}
150-
151-
public boolean joinRoom(MatrixRoomId roomId) {
152-
153-
return api.joinRoom(roomId.getFormatted(), "hello there");
154-
}
155-
156-
public boolean leaveRoom(MatrixRoomId roomId) {
157-
158-
return api.leaveRoom(roomId.getFormatted(), "bai");
159-
}
28+
/**
29+
* Sets a consumer object that gets called on events happening on the matrix server.
30+
* Only one consumer can be set at any time.
31+
* Calling this method again replaces any previous event callback.
32+
*
33+
* @param eventConsumer The consumer to call on events.
34+
*/
35+
void setEventCallback(MatrixEventConsumer eventConsumer);
36+
37+
/**
38+
* Optionally provides an interface to provide the current state of the matrix client.
39+
* If not provided, any startup will act like the first startup and will ignore any previously sent messages.
40+
* Providing a persisted state will make the client be able to determine which events happened while offline.
41+
*
42+
* @param persistedState An interface for persisting the matrix client state
43+
*/
44+
void setPersistedStateProvider(MatrixPersistedStateProvider persistedState);
45+
46+
47+
/**
48+
* The main matrix client event loop that continuously syncs all events happening on the matrix server to the client.
49+
* This is a blocking call, so make sure to call it from a different thread if needed.
50+
*
51+
* @throws InterruptedException The sync has been interrupted
52+
*/
53+
void syncContinuous() throws InterruptedException;
54+
55+
/**
56+
* Requests the matrix client to stop syncing and terminate.
57+
* May be called from a different thread.
58+
*/
59+
void requestStopOfSync();
60+
61+
62+
/**
63+
* Returns whether the matrix client is currently connected to the server or not.
64+
*
65+
* @return {@code true} if the client is currently connected to the server, {@code false} otherwise.
66+
*/
67+
boolean isConnected();
68+
69+
/**
70+
* Returns the current state of the matrix client.
71+
*
72+
* @return A {@link MatrixState} object if currently connected to a server, {@link Optional#empty()} otherwise.
73+
*/
74+
Optional<MatrixState> getState();
75+
76+
/**
77+
* Attempts to send a message to the specified room.
78+
*
79+
* @param roomId The id of the room to send the message to.
80+
* @param messageBody The body of the message to send.
81+
* @return A {@link MatrixEventId} containing the id of the event that was sent or {@link Optional#empty()} if sending the message did not succeed.
82+
*/
83+
Optional<MatrixEventId> sendMessage(MatrixRoomId roomId, String messageBody);
84+
85+
/**
86+
* Attempts to add a reaction to an event (a message of the time).
87+
*
88+
* @param roomId The id of the room to send the message in.
89+
* @param eventId The id of the event to react to.
90+
* @param reaction The reaction to send.
91+
* @return A {@link MatrixEventId} containing the id of the event that was sent or {@link Optional#empty()} if sending the reaction did not succeed.
92+
*/
93+
Optional<MatrixEventId> addReaction(MatrixRoomId roomId, MatrixEventId eventId, String reaction);
94+
95+
/**
96+
* Attempts to join a room.
97+
*
98+
* @param roomId The id of the room to join.
99+
* @return {@code true} if joining the room was successful, {@code false} otherwise.
100+
*/
101+
boolean joinRoom(MatrixRoomId roomId);
102+
103+
/**
104+
* Attempts to leave a room.
105+
*
106+
* @param roomId The id of the room to leave.
107+
* @return {@code true} if leaving the room was successful, {@code false} otherwise.
108+
*/
109+
boolean leaveRoom(MatrixRoomId roomId);
160110
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.synyx.matrix.bot;
2+
3+
/**
4+
* An exception that was not recoverable from by the matrix client itself occurred while communicating with the matrix server.
5+
*/
6+
public class MatrixCommunicationException extends RuntimeException {
7+
8+
public MatrixCommunicationException(String message) {
9+
10+
super(message);
11+
}
12+
13+
public MatrixCommunicationException(String message, Throwable cause) {
14+
15+
super(message, cause);
16+
}
17+
}

src/main/java/org/synyx/matrix/bot/MatrixEventConsumer.java

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,76 @@
55
import org.synyx.matrix.bot.domain.MatrixRoomId;
66
import org.synyx.matrix.bot.domain.MatrixRoomInvite;
77
import org.synyx.matrix.bot.domain.MatrixUserId;
8+
import org.synyx.matrix.bot.internal.MatrixClientImpl;
89

10+
/**
11+
* An interface providing callbacks for things happening on the matrix server that were received by the client.
12+
* All methods have a default implementation that does nothing, so implementing classes only need to override whatever
13+
* they want to listen to.
14+
* <p>
15+
* Any reactions to events happening shall be performed using the appropriate {@link MatrixClientImpl} instance.
16+
*/
917
public interface MatrixEventConsumer {
1018

11-
default void onConnected(MatrixState state) {
19+
/**
20+
* The client successfully connected to the server.
21+
*
22+
* @param state The state after the initial synchronisation.
23+
*/
24+
default void onConnected(MatrixState state) {
1225

13-
}
26+
}
1427

15-
default void onMessage(MatrixState state, MatrixRoom room, MatrixMessage message) {
28+
/**
29+
* A message event was received in a room that the client is part of.
30+
*
31+
* @param state The current client state.
32+
* @param room The room the message was received in.
33+
* @param message The message that was received.
34+
*/
35+
default void onMessage(MatrixState state, MatrixRoom room, MatrixMessage message) {
1636

17-
}
37+
}
1838

19-
default void onInviteToRoom(MatrixState state, MatrixRoomInvite invite) {
39+
/**
40+
* An invitation to a room was received.
41+
*
42+
* @param state The current client state.
43+
* @param invite The invite that was received.
44+
*/
45+
default void onInviteToRoom(MatrixState state, MatrixRoomInvite invite) {
2046

21-
}
47+
}
2248

23-
default void onUserJoinRoom(MatrixState state, MatrixRoom room, MatrixUserId userId) {
49+
/**
50+
* A user joined a room that the client is part of.
51+
*
52+
* @param state The current client state.
53+
* @param room The room that the user joined in.
54+
* @param userId The id of the user that joined the room.
55+
*/
56+
default void onUserJoinRoom(MatrixState state, MatrixRoom room, MatrixUserId userId) {
2457

25-
}
58+
}
2659

27-
default void onUserLeaveRoom(MatrixState state, MatrixRoom room, MatrixUserId userId) {
60+
/**
61+
* A user left a room that the client is part of.
62+
*
63+
* @param state The current client state.
64+
* @param room The room that the user left from.
65+
* @param userId The id of the user that left the room.
66+
*/
67+
default void onUserLeaveRoom(MatrixState state, MatrixRoom room, MatrixUserId userId) {
2868

29-
}
69+
}
3070

31-
default void onSelfLeaveRoom(MatrixState state, MatrixRoomId roomId) {
71+
/**
72+
* The client left a room it was part of. May have been caused by external factors like kicks or bans.
73+
*
74+
* @param state The current client state.
75+
* @param roomId The id of the room that the client left from.
76+
*/
77+
default void onSelfLeaveRoom(MatrixState state, MatrixRoomId roomId) {
3278

33-
}
79+
}
3480
}

src/main/java/org/synyx/matrix/bot/MatrixPersistedState.java

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.synyx.matrix.bot;
2+
3+
import java.util.Optional;
4+
5+
public interface MatrixPersistedStateProvider {
6+
7+
Optional<String> getLastBatch();
8+
9+
void setLastBatch(String value);
10+
}

0 commit comments

Comments
 (0)