Skip to content

Commit

Permalink
Add events service
Browse files Browse the repository at this point in the history
  • Loading branch information
lucko committed Feb 18, 2024
1 parent a35f475 commit 4611fa7
Show file tree
Hide file tree
Showing 17 changed files with 829 additions and 1 deletion.
5 changes: 4 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ version = '0.1-SNAPSHOT'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
languageVersion = JavaLanguageVersion.of(17)
}
withJavadocJar()
withSourcesJar()
Expand All @@ -29,6 +29,9 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
implementation 'com.squareup.okio:okio:1.17.5'
implementation 'com.google.code.gson:gson:2.9.1'
implementation('com.launchdarkly:okhttp-eventsource:4.1.1') {
exclude(module: 'okhttp')
}

testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
Expand Down
187 changes: 187 additions & 0 deletions src/main/java/net/luckperms/rest/EventCallAdapterFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <[email protected]>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package net.luckperms.rest;

import com.google.gson.Gson;
import com.launchdarkly.eventsource.ConnectStrategy;
import com.launchdarkly.eventsource.EventSource;
import com.launchdarkly.eventsource.FaultEvent;
import com.launchdarkly.eventsource.MessageEvent;
import com.launchdarkly.eventsource.StreamEvent;
import net.luckperms.rest.event.EventCall;
import net.luckperms.rest.event.EventProducer;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.CallAdapter;
import retrofit2.Retrofit;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;

class EventCallAdapterFactory extends CallAdapter.Factory {
private final OkHttpClient client;

EventCallAdapterFactory(OkHttpClient client) {
this.client = client;
}

@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != EventCall.class) {
return null;
}

if (!(returnType instanceof ParameterizedType)) {
throw new IllegalArgumentException("Return type must be parameterized as EventCall<Foo> or EventCall<? extends Foo>");
}

Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);

return new CallAdapter<Object, EventCall<?>>() {
@Override
public Type responseType() {
return Object.class;
}

@Override
public EventCall<Object> adapt(Call<Object> call) {
return new EventCallImpl<>(call.request().url(), responseType, EventCallAdapterFactory.this.client);
}
};
}

private static final class EventCallImpl<E> implements EventCall<E> {
private final HttpUrl url;
private final Type responseType;
private final OkHttpClient client;

EventCallImpl(HttpUrl url, Type responseType, OkHttpClient client) {
this.url = url;
this.responseType = responseType;
this.client = client;
}

@Override
public EventProducer<E> subscribe() throws Exception {
EventSource eventSource = new EventSource.Builder(ConnectStrategy.http(this.url).httpClient(this.client)).build();
eventSource.start();

return new EventProducerImpl<>(eventSource, this.responseType);
}
}

private static final class EventProducerImpl<E> implements EventProducer<E> {
private static final Gson GSON = new Gson();

private final EventSource eventSource;
private final Type responseType;

private final List<Consumer<E>> handlers;
private final List<Consumer<Exception>> errorHandlers;

private EventProducerImpl(EventSource eventSource, Type responseType) {
this.eventSource = eventSource;
this.responseType = responseType;
this.handlers = new CopyOnWriteArrayList<>();
this.errorHandlers = new CopyOnWriteArrayList<>();

startListening();
}

private void startListening() {
// TODO: should probably use a different pool
CompletableFuture.runAsync(() -> {
try {
for (StreamEvent event : this.eventSource.anyEvents()) {
if (event instanceof MessageEvent) {
handleMessage((MessageEvent) event);
} else if (event instanceof FaultEvent) {
handleError((FaultEvent) event);
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
}

private void handleMessage(MessageEvent e) {
// TODO: fix logging in this method
String eventName = e.getEventName();
if (!eventName.equals("message")) {
return;
}

E parsedEvent;
try {
parsedEvent = GSON.fromJson(e.getData(), this.responseType);
} catch (Exception ex) {
ex.printStackTrace();
return;
}

for (Consumer<E> handler : this.handlers) {
try {
handler.accept(parsedEvent);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

private void handleError(FaultEvent e) {
for (Consumer<Exception> errorHandler : this.errorHandlers) {
try {
errorHandler.accept(e.getCause());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

@Override
public void subscribe(Consumer<E> consumer) {
this.handlers.add(consumer);
}

@Override
public void errorHandler(Consumer<Exception> errorHandler) {
this.errorHandlers.add(errorHandler);
}

@Override
public void close() {
this.eventSource.close();
}
}

}
8 changes: 8 additions & 0 deletions src/main/java/net/luckperms/rest/LuckPermsRestClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package net.luckperms.rest;

import net.luckperms.rest.service.ActionService;
import net.luckperms.rest.service.EventService;
import net.luckperms.rest.service.GroupService;
import net.luckperms.rest.service.MiscService;
import net.luckperms.rest.service.TrackService;
Expand Down Expand Up @@ -75,6 +76,13 @@ static Builder builder() {
*/
ActionService actions();

/**
* Gets the event service.
*
* @return the event service
*/
EventService events();

/**
* Gets the misc service.
*
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/net/luckperms/rest/LuckPermsRestClientImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package net.luckperms.rest;

import net.luckperms.rest.service.ActionService;
import net.luckperms.rest.service.EventService;
import net.luckperms.rest.service.GroupService;
import net.luckperms.rest.service.MiscService;
import net.luckperms.rest.service.TrackService;
Expand All @@ -39,6 +40,7 @@

import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

class LuckPermsRestClientImpl implements LuckPermsRestClient {
private final OkHttpClient httpClient;
Expand All @@ -47,6 +49,7 @@ class LuckPermsRestClientImpl implements LuckPermsRestClient {
private final GroupService groupService;
private final TrackService trackService;
private final ActionService actionService;
private final EventService eventService;
private final MiscService miscService;

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

clientBuilder.readTimeout(60, TimeUnit.SECONDS);

this.httpClient = clientBuilder.build();

Retrofit retrofit = new Retrofit.Builder()
.client(this.httpClient)
.baseUrl(baseUrl)
.addCallAdapterFactory(new EventCallAdapterFactory(this.httpClient))
.addConverterFactory(GsonConverterFactory.create())
.build();

this.userService = retrofit.create(UserService.class);
this.groupService = retrofit.create(GroupService.class);
this.trackService = retrofit.create(TrackService.class);
this.actionService = retrofit.create(ActionService.class);
this.eventService = retrofit.create(EventService.class);
this.miscService = retrofit.create(MiscService.class);
}

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

@Override
public EventService events() {
return this.eventService;
}

@Override
public MiscService misc() {
return this.miscService;
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/net/luckperms/rest/event/EventCall.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <[email protected]>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package net.luckperms.rest.event;

public interface EventCall<E> {

EventProducer<E> subscribe() throws Exception;

}
39 changes: 39 additions & 0 deletions src/main/java/net/luckperms/rest/event/EventProducer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <[email protected]>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package net.luckperms.rest.event;

import java.util.function.Consumer;

public interface EventProducer<E> extends AutoCloseable {

void subscribe(Consumer<E> consumer);

void errorHandler(Consumer<Exception> errorHandler);

@Override
void close();

}
Loading

0 comments on commit 4611fa7

Please sign in to comment.