handler() {
17 | return responseInfo -> HttpResponse.BodySubscribers.replacing(new ResponseInfo(responseInfo));
18 | }
19 |
20 | public ResponseInfo assertSuccess() {
21 | int statusCode = responseInfo.statusCode();
22 | if (statusCode >= 200 && statusCode < 400) {
23 | return this;
24 | }
25 | throw new IllegalStateException(String.format("Response failed with code %s", statusCode));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/cosium/matrix_communication_client/RoomResource.java:
--------------------------------------------------------------------------------
1 | package com.cosium.matrix_communication_client;
2 |
3 | /**
4 | * @author Réda Housni Alaoui
5 | */
6 | public interface RoomResource {
7 |
8 | String id();
9 |
10 | /**
11 | * https://spec.matrix.org/latest/client-server-api/#mroommessage
13 | */
14 | ClientEventResource sendMessage(Message messageInput);
15 |
16 | /**
17 | * @param dir The direction to return events from. If this is set to f, events will be returned in
18 | * chronological order starting at from. If it is set to b, events will be returned in reverse
19 | * chronological order, again starting at from. One of: [b, f].
20 | * @param from The token to start returning events from. This token can be obtained from a
21 | * prev_batch or next_batch token returned by the /sync endpoint, or from an end token
22 | * returned by a previous request to this endpoint.
23 | * This endpoint can also accept a value returned as a start token by a previous request to
24 | * this endpoint, though servers are not required to support this. Clients should not rely on
25 | * the behaviour.
26 | *
If it is not provided, the homeserver shall return a list of messages from the first or
27 | * last (per the value of the dir parameter) visible event in the room history for the
28 | * requesting user.
29 | * @param limit The maximum number of events to return.
30 | * @param to The token to stop returning events at. This token can be obtained from a prev_batch
31 | * or next_batch token returned by the /sync endpoint, or from an end token returned by a
32 | * previous request to this endpoint.
33 | * @return A page of events
34 | */
35 | ClientEventPage fetchEventPage(String dir, String from, Long limit, String to);
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/cosium/matrix_communication_client/RoomsResource.java:
--------------------------------------------------------------------------------
1 | package com.cosium.matrix_communication_client;
2 |
3 | /**
4 | * @author Réda Housni Alaoui
5 | */
6 | public interface RoomsResource {
7 |
8 | RoomResource byId(String id);
9 |
10 | RoomResource create(CreateRoomInput input);
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/cosium/matrix_communication_client/SimpleAccessTokensResource.java:
--------------------------------------------------------------------------------
1 | package com.cosium.matrix_communication_client;
2 |
3 | import static java.util.Objects.requireNonNull;
4 |
5 | /**
6 | * @author Réda Housni Alaoui
7 | */
8 | class SimpleAccessTokensResource implements AccessTokensResource {
9 |
10 | private final AccessTokenFactory accessTokenFactory;
11 |
12 | SimpleAccessTokensResource(AccessTokenFactory accessTokenFactory) {
13 | this.accessTokenFactory = requireNonNull(accessTokenFactory);
14 | }
15 |
16 | @Override
17 | public String create() {
18 | return accessTokenFactory.build();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/cosium/matrix_communication_client/SimpleClientEventResource.java:
--------------------------------------------------------------------------------
1 | package com.cosium.matrix_communication_client;
2 |
3 | import static java.util.Objects.requireNonNull;
4 |
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 |
7 | /**
8 | * @author Réda Housni Alaoui
9 | */
10 | class SimpleClientEventResource implements ClientEventResource {
11 |
12 | private final Lazy api;
13 | private final ObjectMapper objectMapper;
14 | private final String roomId;
15 | private final String id;
16 |
17 | public SimpleClientEventResource(
18 | Lazy api, ObjectMapper objectMapper, String roomId, String id) {
19 | this.api = requireNonNull(api);
20 | this.objectMapper = requireNonNull(objectMapper);
21 | this.roomId = requireNonNull(roomId);
22 | this.id = requireNonNull(id);
23 | }
24 |
25 | @Override
26 | public ClientEvent fetch() {
27 | return new ClientEvent(objectMapper, api.get().fetchEvent(roomId, id));
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/cosium/matrix_communication_client/SimpleMatrixResources.java:
--------------------------------------------------------------------------------
1 | package com.cosium.matrix_communication_client;
2 |
3 | import com.fasterxml.jackson.databind.DeserializationFeature;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import java.time.Duration;
6 |
7 | /**
8 | * @author Réda Housni Alaoui
9 | */
10 | class SimpleMatrixResources implements MatrixResources {
11 |
12 | private final ObjectMapper objectMapper;
13 | private final AccessTokenFactory accessTokenFactory;
14 | private final Lazy api;
15 |
16 | public SimpleMatrixResources(
17 | boolean https,
18 | String hostname,
19 | Integer port,
20 | Duration connectTimeout,
21 | AccessTokenFactoryFactory accessTokenFactoryFactory) {
22 | objectMapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
23 | MatrixUris matrixUris = new MatrixUris(https, new MatrixHostname(hostname), port);
24 | JsonHandlers jsonHandlers = new JsonHandlers(objectMapper);
25 | HttpClientFactory httpClientFactory = new HttpClientFactory(connectTimeout);
26 | accessTokenFactory =
27 | accessTokenFactoryFactory.build(httpClientFactory, jsonHandlers, matrixUris);
28 | api =
29 | Lazy.of(
30 | () -> MatrixApi.load(httpClientFactory, jsonHandlers, matrixUris, accessTokenFactory));
31 | }
32 |
33 | @Override
34 | public AccessTokensResource accessTokens() {
35 | return new SimpleAccessTokensResource(accessTokenFactory);
36 | }
37 |
38 | @Override
39 | public RoomsResource rooms() {
40 | return new SimpleRoomsResource(api, objectMapper);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/cosium/matrix_communication_client/SimpleRoomResource.java:
--------------------------------------------------------------------------------
1 | package com.cosium.matrix_communication_client;
2 |
3 | import static java.util.Objects.requireNonNull;
4 | import static java.util.Optional.ofNullable;
5 |
6 | import com.fasterxml.jackson.databind.ObjectMapper;
7 |
8 | /**
9 | * @author Réda Housni Alaoui
10 | */
11 | class SimpleRoomResource implements RoomResource {
12 |
13 | private final Lazy api;
14 | private final ObjectMapper objectMapper;
15 | private final String id;
16 |
17 | public SimpleRoomResource(Lazy api, ObjectMapper objectMapper, String id) {
18 | this.api = requireNonNull(api);
19 | this.objectMapper = requireNonNull(objectMapper);
20 | this.id = requireNonNull(id);
21 | }
22 |
23 | @Override
24 | public String id() {
25 | return id;
26 | }
27 |
28 | @Override
29 | public ClientEventResource sendMessage(Message message) {
30 | CreatedEvent createdEvent = api.get().sendMessageToRoom(message, id);
31 | return new SimpleClientEventResource(api, objectMapper, id, createdEvent.id());
32 | }
33 |
34 | @Override
35 | public ClientEventPage fetchEventPage(String dir, String from, Long limit, String to) {
36 | RawClientEventPage raw =
37 | api.get()
38 | .fetchMessagePage(
39 | id, dir, from, ofNullable(limit).map(String::valueOf).orElse(null), to);
40 | return new ClientEventPage(objectMapper, raw);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/cosium/matrix_communication_client/SimpleRoomsResource.java:
--------------------------------------------------------------------------------
1 | package com.cosium.matrix_communication_client;
2 |
3 | import static java.util.Objects.requireNonNull;
4 |
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 |
7 | /**
8 | * @author Réda Housni Alaoui
9 | */
10 | class SimpleRoomsResource implements RoomsResource {
11 |
12 | private final Lazy api;
13 | private final ObjectMapper objectMapper;
14 |
15 | SimpleRoomsResource(Lazy api, ObjectMapper objectMapper) {
16 | this.api = requireNonNull(api);
17 | this.objectMapper = requireNonNull(objectMapper);
18 | }
19 |
20 | @Override
21 | public RoomResource byId(String id) {
22 | return new SimpleRoomResource(api, objectMapper, id);
23 | }
24 |
25 | @Override
26 | public RoomResource create(CreateRoomInput input) {
27 | return new SimpleRoomResource(api, objectMapper, api.get().createRoom(input).roomId());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/cosium/matrix_communication_client/UsernamePasswordAccessTokenFactory.java:
--------------------------------------------------------------------------------
1 | package com.cosium.matrix_communication_client;
2 |
3 | /**
4 | * @author Réda Housni Alaoui
5 | */
6 | class UsernamePasswordAccessTokenFactory implements AccessTokenFactory {
7 |
8 | private final Lazy accessToken;
9 |
10 | public UsernamePasswordAccessTokenFactory(
11 | Lazy api, String username, String password) {
12 | accessToken =
13 | Lazy.of(
14 | () ->
15 | api.get()
16 | .login(
17 | new LoginInput(
18 | "m.login.password",
19 | new LoginInput.Identifier("m.id.user", username),
20 | password))
21 | .accessToken());
22 | }
23 |
24 | @Override
25 | public String build() {
26 | return accessToken.get();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/cosium/matrix_communication_client/UsernamePasswordAccessTokenFactoryFactory.java:
--------------------------------------------------------------------------------
1 | package com.cosium.matrix_communication_client;
2 |
3 | import static java.util.Objects.requireNonNull;
4 |
5 | /**
6 | * @author Réda Housni Alaoui
7 | */
8 | class UsernamePasswordAccessTokenFactoryFactory implements AccessTokenFactoryFactory {
9 |
10 | private final String username;
11 | private final String password;
12 |
13 | UsernamePasswordAccessTokenFactoryFactory(String username, String password) {
14 | this.username = requireNonNull(username);
15 | this.password = requireNonNull(password);
16 | }
17 |
18 | @Override
19 | public AccessTokenFactory build(
20 | HttpClientFactory httpClientFactory, JsonHandlers jsonHandlers, MatrixUris uris) {
21 | return new UsernamePasswordAccessTokenFactory(
22 | Lazy.of(() -> MatrixUnprotectedApi.load(httpClientFactory, jsonHandlers, uris)),
23 | username,
24 | password);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/cosium/matrix_communication_client/WellKnownMatrixClient.java:
--------------------------------------------------------------------------------
1 | package com.cosium.matrix_communication_client;
2 |
3 | import static java.util.Objects.requireNonNull;
4 |
5 | import com.fasterxml.jackson.annotation.JsonCreator;
6 | import com.fasterxml.jackson.annotation.JsonProperty;
7 | import java.io.IOException;
8 | import java.io.UncheckedIOException;
9 | import java.net.http.HttpClient;
10 | import java.net.http.HttpRequest;
11 | import java.util.Optional;
12 |
13 | /**
14 | * @author Réda Housni Alaoui
15 | */
16 | class WellKnownMatrixClient {
17 |
18 | private final HomeServer homeServer;
19 |
20 | @JsonCreator
21 | WellKnownMatrixClient(@JsonProperty("m.homeserver") HomeServer homeServer) {
22 | this.homeServer = requireNonNull(homeServer);
23 | }
24 |
25 | public static Optional fetch(
26 | HttpClient httpClient, JsonHandlers jsonHandlers, MatrixUris matrixUris) {
27 | MatrixUri uri = matrixUris.create(".well-known", "matrix", "client");
28 | try {
29 | JsonBody response =
30 | httpClient
31 | .send(
32 | HttpRequest.newBuilder(uri.toUri())
33 | .header("Accept", "application/json")
34 | .GET()
35 | .build(),
36 | jsonHandlers.handler(WellKnownMatrixClient.class))
37 | .body()
38 | .get();
39 | if (response.isNotFound()) {
40 | return Optional.empty();
41 | }
42 | return Optional.of(response.parse());
43 | } catch (IOException e) {
44 | throw new UncheckedIOException(e);
45 | } catch (InterruptedException e) {
46 | Thread.currentThread().interrupt();
47 | throw new RuntimeException(e);
48 | }
49 | }
50 |
51 | public HomeServer homeServer() {
52 | return homeServer;
53 | }
54 |
55 | public static class HomeServer {
56 | private final String baseUrl;
57 |
58 | @JsonCreator
59 | HomeServer(@JsonProperty("base_url") String baseUrl) {
60 | this.baseUrl = requireNonNull(baseUrl);
61 | }
62 |
63 | public MatrixUri baseUri() {
64 | return new MatrixUri(baseUrl);
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/test/java/com/cosium/matrix_communication_client/AccessTokensResourceTest.java:
--------------------------------------------------------------------------------
1 | package com.cosium.matrix_communication_client;
2 |
3 | import static org.assertj.core.api.Assertions.assertThatCode;
4 |
5 | import com.cosium.synapse_junit_extension.EnableSynapse;
6 | import com.cosium.synapse_junit_extension.Synapse;
7 | import java.util.UUID;
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.junit.jupiter.api.DisplayName;
10 | import org.junit.jupiter.api.Test;
11 |
12 | /**
13 | * @author Réda Housni Alaoui
14 | */
15 | @EnableSynapse
16 | class AccessTokensResourceTest {
17 | private Synapse synapse;
18 |
19 | @BeforeEach
20 | void beforeEach(Synapse synapse) {
21 | this.synapse = synapse;
22 | }
23 |
24 | @Test
25 | @DisplayName("Create room")
26 | void test1() {
27 | String accessToken =
28 | newResourcesBuilder()
29 | .usernamePassword(synapse.adminUsername(), synapse.adminPassword())
30 | .build()
31 | .accessTokens()
32 | .create();
33 |
34 | CreateRoomInput createRoomInput =
35 | CreateRoomInput.builder()
36 | .name(UUID.randomUUID().toString())
37 | .roomAliasName(UUID.randomUUID().toString())
38 | .topic(UUID.randomUUID().toString())
39 | .build();
40 | RoomsResource rooms = newResourcesBuilder().accessToken(accessToken).build().rooms();
41 | assertThatCode(() -> rooms.create(createRoomInput)).doesNotThrowAnyException();
42 | }
43 |
44 | private MatrixResourcesFactory.AuthenticationBuilder newResourcesBuilder() {
45 | return MatrixResources.factory()
46 | .builder()
47 | .https(synapse.https())
48 | .hostname(synapse.hostname())
49 | .port(synapse.port());
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/test/java/com/cosium/matrix_communication_client/LogbackConfigurator.java:
--------------------------------------------------------------------------------
1 | package com.cosium.matrix_communication_client;
2 |
3 | import ch.qos.logback.classic.BasicConfigurator;
4 | import ch.qos.logback.classic.Level;
5 | import ch.qos.logback.classic.LoggerContext;
6 | import java.util.Optional;
7 |
8 | /**
9 | * @author Réda Housni Alaoui
10 | */
11 | public class LogbackConfigurator extends BasicConfigurator {
12 |
13 | @Override
14 | public ExecutionStatus configure(LoggerContext lc) {
15 | super.configure(lc);
16 | Level rootLevel =
17 | Optional.ofNullable(System.getenv("MATRIX_COMMUNICATION_CLIENT_TEST_LOG_ROOT_LEVEL"))
18 | .map(Level::valueOf)
19 | .orElse(Level.OFF);
20 | lc.getLogger("ROOT").setLevel(rootLevel);
21 | return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/java/com/cosium/matrix_communication_client/RoomResourceTest.java:
--------------------------------------------------------------------------------
1 | package com.cosium.matrix_communication_client;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.assertj.core.api.Assertions.assertThatCode;
5 | import static org.assertj.core.api.Assertions.tuple;
6 |
7 | import com.cosium.synapse_junit_extension.EnableSynapse;
8 | import com.cosium.synapse_junit_extension.Synapse;
9 | import java.util.List;
10 | import java.util.UUID;
11 | import org.junit.jupiter.api.BeforeEach;
12 | import org.junit.jupiter.api.DisplayName;
13 | import org.junit.jupiter.api.Test;
14 |
15 | /**
16 | * @author Réda Housni Alaoui
17 | */
18 | @EnableSynapse
19 | class RoomResourceTest {
20 |
21 | private MatrixResources resources;
22 |
23 | @BeforeEach
24 | void beforeEach(Synapse synapse) {
25 | resources =
26 | MatrixResources.factory()
27 | .builder()
28 | .https(synapse.https())
29 | .hostname(synapse.hostname())
30 | .port(synapse.port())
31 | .usernamePassword(synapse.adminUsername(), synapse.adminPassword())
32 | .build();
33 | }
34 |
35 | @Test
36 | @DisplayName("Create room")
37 | void test1() {
38 | assertThatCode(this::createRoom).doesNotThrowAnyException();
39 | }
40 |
41 | @Test
42 | @DisplayName("Send message to room")
43 | void test2() {
44 | Message message = Message.builder().body("body").formattedBody("formattedBody").build();
45 | Message fetchedMessage = createRoom().sendMessage(message).fetch().content(Message.class);
46 |
47 | assertThat(List.of(fetchedMessage))
48 | .extracting(Message::body, Message::format, Message::formattedBody, Message::type)
49 | .containsExactly(
50 | tuple(message.body(), message.format(), message.formattedBody(), message.type()));
51 | }
52 |
53 | @Test
54 | @DisplayName("Fetch event page")
55 | void test3() {
56 | Message message = Message.builder().body("body").formattedBody("formattedBody").build();
57 | RoomResource room = createRoom();
58 | room.sendMessage(message);
59 |
60 | ClientEventPage eventPage = room.fetchEventPage(null, null, null, null);
61 | List fetchedEvents = eventPage.chunk();
62 | assertThat(fetchedEvents)
63 | .filteredOn(clientEvent -> "m.room.message".equals(clientEvent.type()))
64 | .map(clientEvent -> clientEvent.content(Message.class))
65 | .extracting(Message::body, Message::format, Message::formattedBody, Message::type)
66 | .containsExactly(
67 | tuple(message.body(), message.format(), message.formattedBody(), message.type()));
68 | }
69 |
70 | private RoomResource createRoom() {
71 | CreateRoomInput createRoomInput =
72 | CreateRoomInput.builder()
73 | .name(UUID.randomUUID().toString())
74 | .roomAliasName(UUID.randomUUID().toString())
75 | .topic(UUID.randomUUID().toString())
76 | .build();
77 | return resources.rooms().create(createRoomInput);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/test/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator:
--------------------------------------------------------------------------------
1 | com.cosium.matrix_communication_client.LogbackConfigurator
2 |
--------------------------------------------------------------------------------
/src/test/resources/testcontainers.properties:
--------------------------------------------------------------------------------
1 | transport.type=httpclient5
2 |
--------------------------------------------------------------------------------