Skip to content

Commit 4611fa7

Browse files
committed
Add events service
1 parent a35f475 commit 4611fa7

17 files changed

+829
-1
lines changed

build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ version = '0.1-SNAPSHOT'
99

1010
java {
1111
toolchain {
12-
languageVersion = JavaLanguageVersion.of(8)
12+
languageVersion = JavaLanguageVersion.of(17)
1313
}
1414
withJavadocJar()
1515
withSourcesJar()
@@ -29,6 +29,9 @@ dependencies {
2929
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
3030
implementation 'com.squareup.okio:okio:1.17.5'
3131
implementation 'com.google.code.gson:gson:2.9.1'
32+
implementation('com.launchdarkly:okhttp-eventsource:4.1.1') {
33+
exclude(module: 'okhttp')
34+
}
3235

3336
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
3437
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
* This file is part of LuckPerms, licensed under the MIT License.
3+
*
4+
* Copyright (c) lucko (Luck) <[email protected]>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in all
15+
* copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
* SOFTWARE.
24+
*/
25+
26+
package net.luckperms.rest;
27+
28+
import com.google.gson.Gson;
29+
import com.launchdarkly.eventsource.ConnectStrategy;
30+
import com.launchdarkly.eventsource.EventSource;
31+
import com.launchdarkly.eventsource.FaultEvent;
32+
import com.launchdarkly.eventsource.MessageEvent;
33+
import com.launchdarkly.eventsource.StreamEvent;
34+
import net.luckperms.rest.event.EventCall;
35+
import net.luckperms.rest.event.EventProducer;
36+
import okhttp3.HttpUrl;
37+
import okhttp3.OkHttpClient;
38+
import retrofit2.Call;
39+
import retrofit2.CallAdapter;
40+
import retrofit2.Retrofit;
41+
42+
import java.lang.annotation.Annotation;
43+
import java.lang.reflect.ParameterizedType;
44+
import java.lang.reflect.Type;
45+
import java.util.List;
46+
import java.util.concurrent.CompletableFuture;
47+
import java.util.concurrent.CopyOnWriteArrayList;
48+
import java.util.function.Consumer;
49+
50+
class EventCallAdapterFactory extends CallAdapter.Factory {
51+
private final OkHttpClient client;
52+
53+
EventCallAdapterFactory(OkHttpClient client) {
54+
this.client = client;
55+
}
56+
57+
@Override
58+
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
59+
if (getRawType(returnType) != EventCall.class) {
60+
return null;
61+
}
62+
63+
if (!(returnType instanceof ParameterizedType)) {
64+
throw new IllegalArgumentException("Return type must be parameterized as EventCall<Foo> or EventCall<? extends Foo>");
65+
}
66+
67+
Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);
68+
69+
return new CallAdapter<Object, EventCall<?>>() {
70+
@Override
71+
public Type responseType() {
72+
return Object.class;
73+
}
74+
75+
@Override
76+
public EventCall<Object> adapt(Call<Object> call) {
77+
return new EventCallImpl<>(call.request().url(), responseType, EventCallAdapterFactory.this.client);
78+
}
79+
};
80+
}
81+
82+
private static final class EventCallImpl<E> implements EventCall<E> {
83+
private final HttpUrl url;
84+
private final Type responseType;
85+
private final OkHttpClient client;
86+
87+
EventCallImpl(HttpUrl url, Type responseType, OkHttpClient client) {
88+
this.url = url;
89+
this.responseType = responseType;
90+
this.client = client;
91+
}
92+
93+
@Override
94+
public EventProducer<E> subscribe() throws Exception {
95+
EventSource eventSource = new EventSource.Builder(ConnectStrategy.http(this.url).httpClient(this.client)).build();
96+
eventSource.start();
97+
98+
return new EventProducerImpl<>(eventSource, this.responseType);
99+
}
100+
}
101+
102+
private static final class EventProducerImpl<E> implements EventProducer<E> {
103+
private static final Gson GSON = new Gson();
104+
105+
private final EventSource eventSource;
106+
private final Type responseType;
107+
108+
private final List<Consumer<E>> handlers;
109+
private final List<Consumer<Exception>> errorHandlers;
110+
111+
private EventProducerImpl(EventSource eventSource, Type responseType) {
112+
this.eventSource = eventSource;
113+
this.responseType = responseType;
114+
this.handlers = new CopyOnWriteArrayList<>();
115+
this.errorHandlers = new CopyOnWriteArrayList<>();
116+
117+
startListening();
118+
}
119+
120+
private void startListening() {
121+
// TODO: should probably use a different pool
122+
CompletableFuture.runAsync(() -> {
123+
try {
124+
for (StreamEvent event : this.eventSource.anyEvents()) {
125+
if (event instanceof MessageEvent) {
126+
handleMessage((MessageEvent) event);
127+
} else if (event instanceof FaultEvent) {
128+
handleError((FaultEvent) event);
129+
}
130+
}
131+
} catch (Exception e) {
132+
e.printStackTrace();
133+
}
134+
});
135+
}
136+
137+
private void handleMessage(MessageEvent e) {
138+
// TODO: fix logging in this method
139+
String eventName = e.getEventName();
140+
if (!eventName.equals("message")) {
141+
return;
142+
}
143+
144+
E parsedEvent;
145+
try {
146+
parsedEvent = GSON.fromJson(e.getData(), this.responseType);
147+
} catch (Exception ex) {
148+
ex.printStackTrace();
149+
return;
150+
}
151+
152+
for (Consumer<E> handler : this.handlers) {
153+
try {
154+
handler.accept(parsedEvent);
155+
} catch (Exception ex) {
156+
ex.printStackTrace();
157+
}
158+
}
159+
}
160+
161+
private void handleError(FaultEvent e) {
162+
for (Consumer<Exception> errorHandler : this.errorHandlers) {
163+
try {
164+
errorHandler.accept(e.getCause());
165+
} catch (Exception ex) {
166+
ex.printStackTrace();
167+
}
168+
}
169+
}
170+
171+
@Override
172+
public void subscribe(Consumer<E> consumer) {
173+
this.handlers.add(consumer);
174+
}
175+
176+
@Override
177+
public void errorHandler(Consumer<Exception> errorHandler) {
178+
this.errorHandlers.add(errorHandler);
179+
}
180+
181+
@Override
182+
public void close() {
183+
this.eventSource.close();
184+
}
185+
}
186+
187+
}

src/main/java/net/luckperms/rest/LuckPermsRestClient.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
package net.luckperms.rest;
2727

2828
import net.luckperms.rest.service.ActionService;
29+
import net.luckperms.rest.service.EventService;
2930
import net.luckperms.rest.service.GroupService;
3031
import net.luckperms.rest.service.MiscService;
3132
import net.luckperms.rest.service.TrackService;
@@ -75,6 +76,13 @@ static Builder builder() {
7576
*/
7677
ActionService actions();
7778

79+
/**
80+
* Gets the event service.
81+
*
82+
* @return the event service
83+
*/
84+
EventService events();
85+
7886
/**
7987
* Gets the misc service.
8088
*

src/main/java/net/luckperms/rest/LuckPermsRestClientImpl.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
package net.luckperms.rest;
2727

2828
import net.luckperms.rest.service.ActionService;
29+
import net.luckperms.rest.service.EventService;
2930
import net.luckperms.rest.service.GroupService;
3031
import net.luckperms.rest.service.MiscService;
3132
import net.luckperms.rest.service.TrackService;
@@ -39,6 +40,7 @@
3940

4041
import java.io.IOException;
4142
import java.util.Objects;
43+
import java.util.concurrent.TimeUnit;
4244

4345
class LuckPermsRestClientImpl implements LuckPermsRestClient {
4446
private final OkHttpClient httpClient;
@@ -47,6 +49,7 @@ class LuckPermsRestClientImpl implements LuckPermsRestClient {
4749
private final GroupService groupService;
4850
private final TrackService trackService;
4951
private final ActionService actionService;
52+
private final EventService eventService;
5053
private final MiscService miscService;
5154

5255
LuckPermsRestClientImpl(String baseUrl, String apiKey) {
@@ -56,18 +59,22 @@ class LuckPermsRestClientImpl implements LuckPermsRestClient {
5659
clientBuilder.addInterceptor(new AuthInterceptor(apiKey));
5760
}
5861

62+
clientBuilder.readTimeout(60, TimeUnit.SECONDS);
63+
5964
this.httpClient = clientBuilder.build();
6065

6166
Retrofit retrofit = new Retrofit.Builder()
6267
.client(this.httpClient)
6368
.baseUrl(baseUrl)
69+
.addCallAdapterFactory(new EventCallAdapterFactory(this.httpClient))
6470
.addConverterFactory(GsonConverterFactory.create())
6571
.build();
6672

6773
this.userService = retrofit.create(UserService.class);
6874
this.groupService = retrofit.create(GroupService.class);
6975
this.trackService = retrofit.create(TrackService.class);
7076
this.actionService = retrofit.create(ActionService.class);
77+
this.eventService = retrofit.create(EventService.class);
7178
this.miscService = retrofit.create(MiscService.class);
7279
}
7380

@@ -90,6 +97,11 @@ public ActionService actions() {
9097
return this.actionService;
9198
}
9299

100+
@Override
101+
public EventService events() {
102+
return this.eventService;
103+
}
104+
93105
@Override
94106
public MiscService misc() {
95107
return this.miscService;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* This file is part of LuckPerms, licensed under the MIT License.
3+
*
4+
* Copyright (c) lucko (Luck) <[email protected]>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in all
15+
* copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
* SOFTWARE.
24+
*/
25+
26+
package net.luckperms.rest.event;
27+
28+
public interface EventCall<E> {
29+
30+
EventProducer<E> subscribe() throws Exception;
31+
32+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* This file is part of LuckPerms, licensed under the MIT License.
3+
*
4+
* Copyright (c) lucko (Luck) <[email protected]>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in all
15+
* copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
* SOFTWARE.
24+
*/
25+
26+
package net.luckperms.rest.event;
27+
28+
import java.util.function.Consumer;
29+
30+
public interface EventProducer<E> extends AutoCloseable {
31+
32+
void subscribe(Consumer<E> consumer);
33+
34+
void errorHandler(Consumer<Exception> errorHandler);
35+
36+
@Override
37+
void close();
38+
39+
}

0 commit comments

Comments
 (0)