From 412818475606441179a06cb698b5f6caaf842f17 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 13 May 2025 13:08:22 +0100 Subject: [PATCH] WIP: liveobjects experemental implementation --- .../java/io/ably/lib/realtime/Channel.java | 6 +++-- .../java/io/ably/lib/realtime/Channel.java | 5 ++-- .../java/io/ably/lib/objects/LiveObjects.java | 8 ++++++ .../ably/lib/objects/LiveObjectsPlugin.java | 5 ++++ .../io/ably/lib/realtime/AblyRealtime.java | 25 ++++++++++++++++++- .../io/ably/lib/realtime/ChannelBase.java | 22 +++++++++++++++- live-objects/build.gradle.kts | 21 ++++++++++++++++ live-objects/gradle.properties | 4 +++ .../io/ably/lib/objects/DefaultLiveObjects.kt | 12 +++++++++ .../lib/objects/DefaultLiveObjectsPlugin.kt | 8 ++++++ settings.gradle.kts | 1 + 11 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 lib/src/main/java/io/ably/lib/objects/LiveObjects.java create mode 100644 lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java create mode 100644 live-objects/build.gradle.kts create mode 100644 live-objects/gradle.properties create mode 100644 live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt create mode 100644 live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt diff --git a/android/src/main/java/io/ably/lib/realtime/Channel.java b/android/src/main/java/io/ably/lib/realtime/Channel.java index daafe5188..baf086cbc 100644 --- a/android/src/main/java/io/ably/lib/realtime/Channel.java +++ b/android/src/main/java/io/ably/lib/realtime/Channel.java @@ -3,6 +3,8 @@ import io.ably.lib.types.AblyException; import io.ably.lib.types.ChannelOptions; import io.ably.lib.push.PushChannel; +import io.ably.lib.objects.LiveObjectsPlugin; + public class Channel extends ChannelBase { /** @@ -12,8 +14,8 @@ public class Channel extends ChannelBase { */ public final PushChannel push; - Channel(AblyRealtime ably, String name, ChannelOptions options) throws AblyException { - super(ably, name, options); + Channel(AblyRealtime ably, String name, ChannelOptions options, LiveObjectsPlugin liveObjectsPlugin) throws AblyException { + super(ably, name, options, liveObjectsPlugin); this.push = ((io.ably.lib.rest.AblyRest) ably).channels.get(name, options).push; } diff --git a/java/src/main/java/io/ably/lib/realtime/Channel.java b/java/src/main/java/io/ably/lib/realtime/Channel.java index 9c7f64995..b48c929b1 100644 --- a/java/src/main/java/io/ably/lib/realtime/Channel.java +++ b/java/src/main/java/io/ably/lib/realtime/Channel.java @@ -1,11 +1,12 @@ package io.ably.lib.realtime; +import io.ably.lib.objects.LiveObjectsPlugin; import io.ably.lib.types.AblyException; import io.ably.lib.types.ChannelOptions; public class Channel extends ChannelBase { - Channel(AblyRealtime ably, String name, ChannelOptions options) throws AblyException { - super(ably, name, options); + Channel(AblyRealtime ably, String name, ChannelOptions options, LiveObjectsPlugin liveObjectsPlugin) throws AblyException { + super(ably, name, options, liveObjectsPlugin); } public interface MessageListener extends ChannelBase.MessageListener {} diff --git a/lib/src/main/java/io/ably/lib/objects/LiveObjects.java b/lib/src/main/java/io/ably/lib/objects/LiveObjects.java new file mode 100644 index 000000000..dd4f4a8a5 --- /dev/null +++ b/lib/src/main/java/io/ably/lib/objects/LiveObjects.java @@ -0,0 +1,8 @@ +package io.ably.lib.objects; + +import io.ably.lib.types.Callback; + +public interface LiveObjects { + Long getRoot(); + void getRootAsync(Callback callback); +} diff --git a/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java b/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java new file mode 100644 index 000000000..eb5a2134f --- /dev/null +++ b/lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java @@ -0,0 +1,5 @@ +package io.ably.lib.objects; + +public interface LiveObjectsPlugin { + LiveObjects getInstance(String channelName); +} diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index 5c29a0dea..a7ff9c953 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -1,10 +1,14 @@ package io.ably.lib.realtime; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import io.ably.lib.network.HttpEngineFactory; +import io.ably.lib.objects.LiveObjects; +import io.ably.lib.objects.LiveObjectsPlugin; import io.ably.lib.rest.AblyRest; import io.ably.lib.rest.Auth; import io.ably.lib.transport.ConnectionManager; @@ -40,6 +44,13 @@ public class AblyRealtime extends AblyRest { */ public final Channels channels; + /** + * A nullable reference to the LiveObjects plugin. + *

+ * This field is initialized only if the LiveObjects plugin is present in the classpath. + */ + private final LiveObjectsPlugin liveObjectsPlugin; + /** * Constructs a Realtime client object using an Ably API key or token string. *

@@ -72,6 +83,8 @@ public AblyRealtime(ClientOptions options) throws AblyException { } } + liveObjectsPlugin = tryInitializeLiveObjectsPlugin(); + if(options.autoConnect) connection.connect(); } @@ -168,6 +181,16 @@ public interface Channels extends ReadOnlyMap { void release(String channelName); } + private LiveObjectsPlugin tryInitializeLiveObjectsPlugin() { + try { + Class liveObjectsImplementation = Class.forName("io.ably.lib.objects.DefaultLiveObjectsPlugin"); + return (LiveObjectsPlugin) liveObjectsImplementation.getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | + InvocationTargetException e) { + return null; + } + } + private class InternalChannels extends InternalMap implements Channels, ConnectionManager.Channels { /** * Get the named channel; if it does not already exist, @@ -187,7 +210,7 @@ public Channel get(final String channelName, final ChannelOptions channelOptions // We're not using computeIfAbsent because that requires Java 1.8. // Hence there's the slight inefficiency of creating newChannel when it may not be // needed because there is an existingChannel. - final Channel newChannel = new Channel(AblyRealtime.this, channelName, channelOptions); + final Channel newChannel = new Channel(AblyRealtime.this, channelName, channelOptions, liveObjectsPlugin); final Channel existingChannel = map.putIfAbsent(channelName, newChannel); if (existingChannel != null) { diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 028f6b23c..4a1a4ca23 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -13,6 +13,8 @@ import io.ably.lib.http.Http; import io.ably.lib.http.HttpCore; import io.ably.lib.http.HttpUtils; +import io.ably.lib.objects.LiveObjects; +import io.ably.lib.objects.LiveObjectsPlugin; import io.ably.lib.transport.ConnectionManager; import io.ably.lib.transport.ConnectionManager.QueuedMessage; import io.ably.lib.transport.Defaults; @@ -91,6 +93,23 @@ public abstract class ChannelBase extends EventEmitter') to your dependency tree", 400, 40000) + ); + } + + if (objects == null) { + objects = liveObjectsPlugin.getInstance(name); + } + + return objects; + } + /*** * internal * @@ -1285,7 +1304,7 @@ else if(stateChange.current.equals(failureState)) { } } - ChannelBase(AblyRealtime ably, String name, ChannelOptions options) throws AblyException { + ChannelBase(AblyRealtime ably, String name, ChannelOptions options, LiveObjectsPlugin liveObjectsPlugin) throws AblyException { Log.v(TAG, "RealtimeChannel(); channel = " + name); this.ably = ably; this.name = name; @@ -1295,6 +1314,7 @@ else if(stateChange.current.equals(failureState)) { this.attachResume = false; state = ChannelState.initialized; this.decodingContext = new DecodingContext(); + this.liveObjectsPlugin = liveObjectsPlugin; } void onChannelMessage(ProtocolMessage msg) { diff --git a/live-objects/build.gradle.kts b/live-objects/build.gradle.kts new file mode 100644 index 000000000..a6733a2cf --- /dev/null +++ b/live-objects/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + `java-library` + alias(libs.plugins.kotlin.jvm) +} + +repositories { + mavenCentral() +} + +dependencies { + implementation(project(":java")) + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() +} + +kotlin { + explicitApi() +} diff --git a/live-objects/gradle.properties b/live-objects/gradle.properties new file mode 100644 index 000000000..29fa6bdb7 --- /dev/null +++ b/live-objects/gradle.properties @@ -0,0 +1,4 @@ +POM_ARTIFACT_ID=live-objects +POM_NAME=Live Objects plugin for Ably Pub/Sub SDK +POM_DESCRIPTION=Live Objects plugin for Ably Pub/Sub SDK +POM_PACKAGING=jar diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt new file mode 100644 index 000000000..58291de9c --- /dev/null +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt @@ -0,0 +1,12 @@ +package io.ably.lib.objects + +import io.ably.lib.types.Callback + +internal class DefaultLiveObjects(private val channelName: String): LiveObjects { + + override fun getRoot(): Long = channelName.length.toLong() + + override fun getRootAsync(callback: Callback) { + callback.onSuccess(channelName.length.toLong()) + } +} diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt new file mode 100644 index 000000000..028ec8271 --- /dev/null +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt @@ -0,0 +1,8 @@ +package io.ably.lib.objects + +public class DefaultLiveObjectsPlugin(): LiveObjectsPlugin { + override fun greetings(): String = "Hello from Live Objects module" + + override fun getInstance(channelName: String): LiveObjects = DefaultLiveObjects(channelName) + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 7ccfd6f3f..6d7d6ba8c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,3 +15,4 @@ include("network-client-core") include("network-client-default") include("network-client-okhttp") include("pubsub-adapter") +include("live-objects")