From c2ed95814611ac9991493fcbe4a3fb03d505d353 Mon Sep 17 00:00:00 2001 From: lucko Date: Wed, 6 Mar 2024 20:58:19 +0000 Subject: [PATCH] Add events service (#1) --- .github/workflows/ci.yml | 42 +++++ build.gradle | 13 ++ .../net/luckperms/rest/EventCallAdapter.java | 172 ++++++++++++++++++ .../rest/EventCallAdapterFactory.java | 66 +++++++ .../luckperms/rest/LuckPermsRestClient.java | 8 + .../rest/LuckPermsRestClientImpl.java | 15 ++ .../net/luckperms/rest/event/EventCall.java | 32 ++++ .../luckperms/rest/event/EventProducer.java | 39 ++++ .../rest/model/LogBroadcastEvent.java | 60 ++++++ .../net/luckperms/rest/model/NodeType.java | 25 +++ .../rest/model/PlayerSaveResult.java | 25 +++ .../rest/model/PostNetworkSyncEvent.java | 59 ++++++ .../luckperms/rest/model/PostSyncEvent.java | 30 +++ .../rest/model/PreNetworkSyncEvent.java | 53 ++++++ .../luckperms/rest/model/PreSyncEvent.java | 30 +++ .../net/luckperms/rest/model/SyncType.java | 44 +++++ .../model/TemporaryNodeMergeStrategy.java | 25 +++ .../luckperms/rest/service/EventService.java | 53 ++++++ .../luckperms/rest/EventServiceTest.java | 147 +++++++++++++++ 19 files changed, 938 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 src/main/java/net/luckperms/rest/EventCallAdapter.java create mode 100644 src/main/java/net/luckperms/rest/EventCallAdapterFactory.java create mode 100644 src/main/java/net/luckperms/rest/event/EventCall.java create mode 100644 src/main/java/net/luckperms/rest/event/EventProducer.java create mode 100644 src/main/java/net/luckperms/rest/model/LogBroadcastEvent.java create mode 100644 src/main/java/net/luckperms/rest/model/PostNetworkSyncEvent.java create mode 100644 src/main/java/net/luckperms/rest/model/PostSyncEvent.java create mode 100644 src/main/java/net/luckperms/rest/model/PreNetworkSyncEvent.java create mode 100644 src/main/java/net/luckperms/rest/model/PreSyncEvent.java create mode 100644 src/main/java/net/luckperms/rest/model/SyncType.java create mode 100644 src/main/java/net/luckperms/rest/service/EventService.java create mode 100644 src/test/java/me/lucko/luckperms/rest/EventServiceTest.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d4edbe5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: Test and Build + +on: + push: + branches: + - 'main' + tags: + - 'v*' + pull_request: + branches: + - 'main' + +jobs: + build-gradle: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: validate gradle wrapper + uses: gradle/wrapper-validation-action@v1 + + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Run build and tests with Gradle wrapper + run: ./gradlew test build + + - name: Publish test report + uses: mikepenz/action-junit-report@v3 + if: success() || failure() + with: + report_paths: 'build/test-results/test/TEST-*.xml' + annotate_notice: true + detailed_summary: true diff --git a/build.gradle b/build.gradle index d5a701b..7dc9866 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,9 @@ plugins { id 'maven-publish' } +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent + group = 'net.luckperms' version = '0.1-SNAPSHOT' @@ -21,6 +24,13 @@ repositories { test { useJUnitPlatform() + testLogging { + events = [TestLogEvent.PASSED, TestLogEvent.FAILED, TestLogEvent.SKIPPED] + exceptionFormat = TestExceptionFormat.FULL + showExceptions = true + showCauses = true + showStackTraces = true + } } dependencies { @@ -29,6 +39,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' diff --git a/src/main/java/net/luckperms/rest/EventCallAdapter.java b/src/main/java/net/luckperms/rest/EventCallAdapter.java new file mode 100644 index 0000000..b5ac3a1 --- /dev/null +++ b/src/main/java/net/luckperms/rest/EventCallAdapter.java @@ -0,0 +1,172 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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 java.lang.reflect.Type; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; + +class EventCallAdapter implements CallAdapter> { + private final Type eventType; + private final OkHttpClient client; + private final ExecutorService executorService; + + EventCallAdapter(Type eventType, OkHttpClient client, ExecutorService executorService) { + this.eventType = eventType; + this.client = client; + this.executorService = executorService; + } + + @Override + public Type responseType() { + return Object.class; + } + + @Override + public EventCall adapt(Call call) { + return new EventCallImpl<>(call.request().url(), this.eventType, this.client, this.executorService); + } + + private static final class EventCallImpl implements EventCall { + private final HttpUrl url; + private final Type eventType; + private final OkHttpClient client; + private final Executor executor; + + EventCallImpl(HttpUrl url, Type eventType, OkHttpClient client, Executor executor) { + this.url = url; + this.eventType = eventType; + this.client = client; + this.executor = executor; + } + + @Override + public EventProducer subscribe() throws Exception { + EventSource eventSource = new EventSource.Builder(ConnectStrategy.http(this.url).httpClient(this.client)).build(); + eventSource.start(); + + return new EventProducerImpl<>(eventSource, this.eventType, this.executor); + } + } + + private static final class EventProducerImpl implements EventProducer { + private static final Gson GSON = new Gson(); + + private final EventSource eventSource; + private final Type eventType; + + private final List> handlers; + private final List> errorHandlers; + + private EventProducerImpl(EventSource eventSource, Type eventType, Executor executor) { + this.eventSource = eventSource; + this.eventType = eventType; + this.handlers = new CopyOnWriteArrayList<>(); + this.errorHandlers = new CopyOnWriteArrayList<>(); + + executor.execute(this::pollForEvents); + } + + private void pollForEvents() { + try { + for (StreamEvent event : this.eventSource.anyEvents()) { + if (event instanceof MessageEvent) { + handleMessage((MessageEvent) event); + } else if (event instanceof FaultEvent) { + handleError(((FaultEvent) event).getCause()); + } + } + } catch (Exception e) { + handleError(e); + } + } + + private void handleMessage(MessageEvent e) { + String eventName = e.getEventName(); + if (!eventName.equals("message")) { + return; + } + + E parsedEvent; + try { + parsedEvent = GSON.fromJson(e.getData(), this.eventType); + } catch (Exception ex) { + handleError(ex); + return; + } + + for (Consumer handler : this.handlers) { + try { + handler.accept(parsedEvent); + } catch (Exception ex) { + handleError(ex); + } + } + } + + private void handleError(Exception e) { + for (Consumer errorHandler : this.errorHandlers) { + try { + errorHandler.accept(e); + } catch (Exception ex) { + // ignore + } + } + } + + @Override + public void subscribe(Consumer consumer) { + this.handlers.add(consumer); + } + + @Override + public void errorHandler(Consumer errorHandler) { + this.errorHandlers.add(errorHandler); + } + + @Override + public void close() { + this.eventSource.close(); + } + } + +} diff --git a/src/main/java/net/luckperms/rest/EventCallAdapterFactory.java b/src/main/java/net/luckperms/rest/EventCallAdapterFactory.java new file mode 100644 index 0000000..6553e40 --- /dev/null +++ b/src/main/java/net/luckperms/rest/EventCallAdapterFactory.java @@ -0,0 +1,66 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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 net.luckperms.rest.event.EventCall; +import okhttp3.OkHttpClient; +import retrofit2.CallAdapter; +import retrofit2.Retrofit; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +class EventCallAdapterFactory extends CallAdapter.Factory implements AutoCloseable { + private final OkHttpClient client; + private final ExecutorService executorService; + + EventCallAdapterFactory(OkHttpClient client) { + this.client = client; + this.executorService = Executors.newCachedThreadPool(); + } + + @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 or EventCall"); + } + + Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType); + return new EventCallAdapter(responseType, this.client, this.executorService); + } + + @Override + public void close() { + this.executorService.shutdown(); + } +} diff --git a/src/main/java/net/luckperms/rest/LuckPermsRestClient.java b/src/main/java/net/luckperms/rest/LuckPermsRestClient.java index 8db4c65..6979b09 100644 --- a/src/main/java/net/luckperms/rest/LuckPermsRestClient.java +++ b/src/main/java/net/luckperms/rest/LuckPermsRestClient.java @@ -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; @@ -75,6 +76,13 @@ static Builder builder() { */ ActionService actions(); + /** + * Gets the event service. + * + * @return the event service + */ + EventService events(); + /** * Gets the misc service. * diff --git a/src/main/java/net/luckperms/rest/LuckPermsRestClientImpl.java b/src/main/java/net/luckperms/rest/LuckPermsRestClientImpl.java index 9047f9d..800576f 100644 --- a/src/main/java/net/luckperms/rest/LuckPermsRestClientImpl.java +++ b/src/main/java/net/luckperms/rest/LuckPermsRestClientImpl.java @@ -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; @@ -39,14 +40,17 @@ import java.io.IOException; import java.util.Objects; +import java.util.concurrent.TimeUnit; class LuckPermsRestClientImpl implements LuckPermsRestClient { private final OkHttpClient httpClient; + private final EventCallAdapterFactory eventCallAdapterFactory; private final UserService userService; 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) { @@ -56,11 +60,15 @@ class LuckPermsRestClientImpl implements LuckPermsRestClient { clientBuilder.addInterceptor(new AuthInterceptor(apiKey)); } + clientBuilder.readTimeout(60, TimeUnit.SECONDS); + this.httpClient = clientBuilder.build(); + this.eventCallAdapterFactory = new EventCallAdapterFactory(this.httpClient); Retrofit retrofit = new Retrofit.Builder() .client(this.httpClient) .baseUrl(baseUrl) + .addCallAdapterFactory(this.eventCallAdapterFactory) .addConverterFactory(GsonConverterFactory.create()) .build(); @@ -68,6 +76,7 @@ class LuckPermsRestClientImpl implements LuckPermsRestClient { 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); } @@ -90,6 +99,11 @@ public ActionService actions() { return this.actionService; } + @Override + public EventService events() { + return this.eventService; + } + @Override public MiscService misc() { return this.miscService; @@ -99,6 +113,7 @@ public MiscService misc() { public void close() { this.httpClient.dispatcher().executorService().shutdown(); this.httpClient.connectionPool().evictAll(); + this.eventCallAdapterFactory.close(); } static final class BuilderImpl implements Builder { diff --git a/src/main/java/net/luckperms/rest/event/EventCall.java b/src/main/java/net/luckperms/rest/event/EventCall.java new file mode 100644 index 0000000..4e0200a --- /dev/null +++ b/src/main/java/net/luckperms/rest/event/EventCall.java @@ -0,0 +1,32 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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 { + + EventProducer subscribe() throws Exception; + +} diff --git a/src/main/java/net/luckperms/rest/event/EventProducer.java b/src/main/java/net/luckperms/rest/event/EventProducer.java new file mode 100644 index 0000000..9710555 --- /dev/null +++ b/src/main/java/net/luckperms/rest/event/EventProducer.java @@ -0,0 +1,39 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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 extends AutoCloseable { + + void subscribe(Consumer consumer); + + void errorHandler(Consumer errorHandler); + + @Override + void close(); + +} diff --git a/src/main/java/net/luckperms/rest/model/LogBroadcastEvent.java b/src/main/java/net/luckperms/rest/model/LogBroadcastEvent.java new file mode 100644 index 0000000..9a83001 --- /dev/null +++ b/src/main/java/net/luckperms/rest/model/LogBroadcastEvent.java @@ -0,0 +1,60 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.model; + +import com.google.gson.annotations.SerializedName; + +public class LogBroadcastEvent extends AbstractModel { + + private final Action entry; + private final Origin origin; + + public LogBroadcastEvent(Action entry, Origin origin) { + this.entry = entry; + this.origin = origin; + } + + public Action entry() { + return this.entry; + } + + public Origin origin() { + return this.origin; + } + + public enum Origin { + + @SerializedName("local") + LOCAL, + + @SerializedName("local_api") + LOCAL_API, + + @SerializedName("remote") + REMOTE; + } + +} diff --git a/src/main/java/net/luckperms/rest/model/NodeType.java b/src/main/java/net/luckperms/rest/model/NodeType.java index 9501244..ad7a070 100644 --- a/src/main/java/net/luckperms/rest/model/NodeType.java +++ b/src/main/java/net/luckperms/rest/model/NodeType.java @@ -1,3 +1,28 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.model; import com.google.gson.annotations.SerializedName; diff --git a/src/main/java/net/luckperms/rest/model/PlayerSaveResult.java b/src/main/java/net/luckperms/rest/model/PlayerSaveResult.java index e3f9fbc..90f7507 100644 --- a/src/main/java/net/luckperms/rest/model/PlayerSaveResult.java +++ b/src/main/java/net/luckperms/rest/model/PlayerSaveResult.java @@ -1,3 +1,28 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.model; import com.google.gson.annotations.SerializedName; diff --git a/src/main/java/net/luckperms/rest/model/PostNetworkSyncEvent.java b/src/main/java/net/luckperms/rest/model/PostNetworkSyncEvent.java new file mode 100644 index 0000000..db3360a --- /dev/null +++ b/src/main/java/net/luckperms/rest/model/PostNetworkSyncEvent.java @@ -0,0 +1,59 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.model; + +import java.util.UUID; + +public class PostNetworkSyncEvent extends AbstractModel { + + private final UUID syncId; + private final SyncType type; + private final boolean didSyncOccur; + private final UUID specificUserUniqueId; // nullable + + public PostNetworkSyncEvent(UUID syncId, SyncType type, boolean didSyncOccur, UUID specificUserUniqueId) { + this.syncId = syncId; + this.type = type; + this.didSyncOccur = didSyncOccur; + this.specificUserUniqueId = specificUserUniqueId; + } + + public UUID syncId() { + return this.syncId; + } + + public SyncType type() { + return this.type; + } + + public boolean didSyncOccur() { + return this.didSyncOccur; + } + + public UUID specificUserUniqueId() { + return this.specificUserUniqueId; + } +} diff --git a/src/main/java/net/luckperms/rest/model/PostSyncEvent.java b/src/main/java/net/luckperms/rest/model/PostSyncEvent.java new file mode 100644 index 0000000..416cd69 --- /dev/null +++ b/src/main/java/net/luckperms/rest/model/PostSyncEvent.java @@ -0,0 +1,30 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.model; + +public class PostSyncEvent extends AbstractModel { + +} diff --git a/src/main/java/net/luckperms/rest/model/PreNetworkSyncEvent.java b/src/main/java/net/luckperms/rest/model/PreNetworkSyncEvent.java new file mode 100644 index 0000000..ad00f64 --- /dev/null +++ b/src/main/java/net/luckperms/rest/model/PreNetworkSyncEvent.java @@ -0,0 +1,53 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.model; + +import java.util.UUID; + +public class PreNetworkSyncEvent extends AbstractModel { + + private final UUID syncId; + private final SyncType type; + private final UUID specificUserUniqueId; // nullable + + public PreNetworkSyncEvent(UUID syncId, SyncType type, UUID specificUserUniqueId) { + this.syncId = syncId; + this.type = type; + this.specificUserUniqueId = specificUserUniqueId; + } + + public UUID syncId() { + return this.syncId; + } + + public SyncType type() { + return this.type; + } + + public UUID specificUserUniqueId() { + return this.specificUserUniqueId; + } +} diff --git a/src/main/java/net/luckperms/rest/model/PreSyncEvent.java b/src/main/java/net/luckperms/rest/model/PreSyncEvent.java new file mode 100644 index 0000000..e0cf20f --- /dev/null +++ b/src/main/java/net/luckperms/rest/model/PreSyncEvent.java @@ -0,0 +1,30 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.model; + +public class PreSyncEvent extends AbstractModel { + +} diff --git a/src/main/java/net/luckperms/rest/model/SyncType.java b/src/main/java/net/luckperms/rest/model/SyncType.java new file mode 100644 index 0000000..8500933 --- /dev/null +++ b/src/main/java/net/luckperms/rest/model/SyncType.java @@ -0,0 +1,44 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.model; + +import com.google.gson.annotations.SerializedName; + +import java.util.Locale; + +public enum SyncType { + + @SerializedName("full") + FULL, + + @SerializedName("specific_user") + SPECIFIC_USER; + + @Override + public String toString() { + return this.name().toLowerCase(Locale.ROOT); + } +} diff --git a/src/main/java/net/luckperms/rest/model/TemporaryNodeMergeStrategy.java b/src/main/java/net/luckperms/rest/model/TemporaryNodeMergeStrategy.java index 1c99912..f9c1571 100644 --- a/src/main/java/net/luckperms/rest/model/TemporaryNodeMergeStrategy.java +++ b/src/main/java/net/luckperms/rest/model/TemporaryNodeMergeStrategy.java @@ -1,3 +1,28 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.model; import com.google.gson.annotations.SerializedName; diff --git a/src/main/java/net/luckperms/rest/service/EventService.java b/src/main/java/net/luckperms/rest/service/EventService.java new file mode 100644 index 0000000..ff1166b --- /dev/null +++ b/src/main/java/net/luckperms/rest/service/EventService.java @@ -0,0 +1,53 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.service; + +import net.luckperms.rest.event.EventCall; +import net.luckperms.rest.model.LogBroadcastEvent; +import net.luckperms.rest.model.PostNetworkSyncEvent; +import net.luckperms.rest.model.PostSyncEvent; +import net.luckperms.rest.model.PreNetworkSyncEvent; +import net.luckperms.rest.model.PreSyncEvent; +import retrofit2.http.GET; + +public interface EventService { + + @GET("/event/log-broadcast") + EventCall logBroadcast(); + + @GET("/event/post-network-sync") + EventCall postNetworkSync(); + + @GET("/event/post-sync") + EventCall postSync(); + + @GET("/event/pre-network-sync") + EventCall preNetworkSync(); + + @GET("/event/pre-sync") + EventCall preSync(); + +} diff --git a/src/test/java/me/lucko/luckperms/rest/EventServiceTest.java b/src/test/java/me/lucko/luckperms/rest/EventServiceTest.java new file mode 100644 index 0000000..6f48da5 --- /dev/null +++ b/src/test/java/me/lucko/luckperms/rest/EventServiceTest.java @@ -0,0 +1,147 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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 me.lucko.luckperms.rest; + +import net.luckperms.rest.LuckPermsRestClient; +import net.luckperms.rest.event.EventCall; +import net.luckperms.rest.event.EventProducer; +import net.luckperms.rest.model.Action; +import net.luckperms.rest.model.LogBroadcastEvent; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; + +public class EventServiceTest extends AbstractIntegrationTest { + + @Test + public void testLogBroadcastEvent() throws Exception { + LuckPermsRestClient client = createClient(); + + Action exampleAction = new Action( + System.currentTimeMillis() / 1000L, + new Action.Source(UUID.randomUUID(), randomName()), + new Action.Target(UUID.randomUUID(), randomName(), Action.Target.Type.USER), + "hello world" + ); + + EventCall call = client.events().logBroadcast(); + LogBroadcastEvent event = testEvent(call, 5, () -> { + try { + client.actions().submit(exampleAction).execute(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + assertEquals(LogBroadcastEvent.Origin.LOCAL_API, event.origin()); + assertEquals(exampleAction, event.entry()); + assertNotSame(event.entry(), exampleAction); + } + + @Test + @Disabled("takes too long to run") + public void testLogBroadcastEventLongWait() throws Exception { + LuckPermsRestClient client = createClient(); + + Action exampleAction = new Action( + System.currentTimeMillis() / 1000L, + new Action.Source(UUID.randomUUID(), randomName()), + new Action.Target(UUID.randomUUID(), randomName(), Action.Target.Type.USER), + "hello world" + ); + + EventCall call = client.events().logBroadcast(); + LogBroadcastEvent event = testEvent(call, 110, () -> { + try { + Thread.sleep(100_000); + client.actions().submit(exampleAction).execute(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + + assertEquals(LogBroadcastEvent.Origin.LOCAL_API, event.origin()); + assertEquals(exampleAction, event.entry()); + assertNotSame(event.entry(), exampleAction); + } + + private static E testEvent(EventCall call, int waitSeconds, Runnable generateEventAction) throws Exception { + // subscribe to the event + EventProducer subscription = call.subscribe(); + AwaitingConsumer consumer = new AwaitingConsumer<>(); + AwaitingConsumer errorConsumer = new AwaitingConsumer<>(); + subscription.subscribe(consumer); + subscription.errorHandler(errorConsumer); + + // cause the event to be fired + CompletableFuture.runAsync(generateEventAction); + + // wait for the event to be generated and received + consumer.await(waitSeconds, TimeUnit.SECONDS); + + // validate a single event was returned + List events = consumer.getResults(); + assertEquals(1, events.size()); + E event = events.get(0); + + // close subscription and assert that there were no errors + subscription.close(); + assertEquals(0, errorConsumer.getResults().size()); + + return event; + } + + private static final class AwaitingConsumer implements Consumer { + private final List results = new ArrayList<>(); + private final CountDownLatch latch = new CountDownLatch(1); + + @Override + public void accept(T obj) { + this.results.add(obj); + this.latch.countDown(); + } + + public List getResults() { + return this.results; + } + + public void await(long timeout, TimeUnit unit) throws InterruptedException { + this.latch.await(timeout, unit); + } + } + +}