Skip to content

Commit 6a4e367

Browse files
committed
[ECO-5386] Defined LiveObjectSerializer interface
1. Added LiveObjectsHelper for initializing LiveObjects plugin and LiveObjectsSerializer impl. 2. Updated ProtocolMessage to handle msgpack serialization/deserialization for state field 3. Implemented LiveObjectsJsonSerializer for json serialization/deserialization of state field
1 parent 6c8a793 commit 6a4e367

7 files changed

Lines changed: 178 additions & 18 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.ably.lib.objects;
2+
3+
import com.google.gson.JsonArray;
4+
import org.jetbrains.annotations.NotNull;
5+
import org.msgpack.core.MessagePacker;
6+
import org.msgpack.core.MessageUnpacker;
7+
8+
import java.io.IOException;
9+
10+
/**
11+
* Serializer interface for converting between LiveObject arrays and their
12+
* MessagePack or JSON representations.
13+
*/
14+
public interface LiveObjectSerializer {
15+
/**
16+
* Reads a MessagePack array from the given unpacker and deserializes it into an Object array.
17+
*
18+
* @param unpacker the MessageUnpacker to read from
19+
* @return the deserialized Object array
20+
* @throws IOException if an I/O error occurs during unpacking
21+
*/
22+
@NotNull
23+
Object[] readMsgpackArray(@NotNull MessageUnpacker unpacker) throws IOException;
24+
25+
/**
26+
* Serializes the given Object array as a MessagePack array using the provided packer.
27+
*
28+
* @param objects the Object array to serialize
29+
* @param packer the MessagePacker to write to
30+
* @throws IOException if an I/O error occurs during packing
31+
*/
32+
void writeMsgpackArray(@NotNull Object[] objects, @NotNull MessagePacker packer) throws IOException;
33+
34+
/**
35+
* Reads a JSON array from the given {@link JsonArray} and deserializes it into an Object array.
36+
*
37+
* @param json the {@link JsonArray} representing the array to deserialize
38+
* @return the deserialized Object array
39+
*/
40+
@NotNull
41+
Object[] readFromJsonArray(@NotNull JsonArray json);
42+
43+
/**
44+
* Serializes the given Object array as a JSON array.
45+
*
46+
* @param objects the Object array to serialize
47+
* @return the resulting JsonArray
48+
*/
49+
@NotNull
50+
JsonArray asJsonArray(@NotNull Object[] objects);
51+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package io.ably.lib.objects;
2+
3+
import io.ably.lib.realtime.AblyRealtime;
4+
import io.ably.lib.util.Log;
5+
6+
import java.lang.reflect.InvocationTargetException;
7+
8+
public class LiveObjectsHelper {
9+
10+
private static final String TAG = LiveObjectsHelper.class.getName();
11+
private static LiveObjectSerializer liveObjectSerializer;
12+
13+
public static LiveObjectsPlugin tryInitializeLiveObjectsPlugin(AblyRealtime ablyRealtime) {
14+
try {
15+
Class<?> liveObjectsImplementation = Class.forName("io.ably.lib.objects.DefaultLiveObjectsPlugin");
16+
LiveObjectsAdapter adapter = new Adapter(ablyRealtime);
17+
return (LiveObjectsPlugin) liveObjectsImplementation
18+
.getDeclaredConstructor(LiveObjectsAdapter.class)
19+
.newInstance(adapter);
20+
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException |
21+
InvocationTargetException e) {
22+
Log.i(TAG, "LiveObjects plugin not found in classpath. LiveObjects functionality will not be available.", e);
23+
return null;
24+
}
25+
}
26+
27+
public static LiveObjectSerializer getLiveObjectSerializer() {
28+
if (liveObjectSerializer == null) {
29+
try {
30+
Class<?> serializerClass = Class.forName("io.ably.lib.objects.DefaultLiveObjectSerializer");
31+
liveObjectSerializer = (LiveObjectSerializer) serializerClass.getDeclaredConstructor().newInstance();
32+
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException |
33+
InvocationTargetException e) {
34+
Log.e(TAG, "Failed to init LiveObjectSerializer, LiveObjects plugin not included in the classpath", e);
35+
return null;
36+
}
37+
}
38+
return liveObjectSerializer;
39+
}
40+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.ably.lib.objects;
2+
3+
import com.google.gson.*;
4+
import io.ably.lib.util.Log;
5+
6+
import java.lang.reflect.Type;
7+
8+
public class LiveObjectsJsonSerializer implements JsonSerializer<Object[]>, JsonDeserializer<Object[]> {
9+
private static final String TAG = LiveObjectsJsonSerializer.class.getName();
10+
private final LiveObjectSerializer serializer = LiveObjectsHelper.getLiveObjectSerializer();
11+
12+
@Override
13+
public Object[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
14+
if (serializer == null) {
15+
Log.w(TAG, "Skipping 'state' field json deserialization because LiveObjectsSerializer not found.");
16+
return null;
17+
}
18+
if (!json.isJsonArray()) {
19+
throw new JsonParseException("Expected a JSON array for 'state' field, but got: " + json);
20+
}
21+
return serializer.readFromJsonArray(json.getAsJsonArray());
22+
}
23+
24+
@Override
25+
public JsonElement serialize(Object[] src, Type typeOfSrc, JsonSerializationContext context) {
26+
if (serializer == null) {
27+
Log.w(TAG, "Skipping 'state' field json serialization because LiveObjectsSerializer not found.");
28+
return JsonNull.INSTANCE;
29+
}
30+
return serializer.asJsonArray(src);
31+
}
32+
}

lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package io.ably.lib.realtime;
22

3-
import java.lang.reflect.InvocationTargetException;
43
import java.util.ArrayList;
54
import java.util.HashMap;
65
import java.util.List;
76
import java.util.Map;
87

9-
import io.ably.lib.objects.Adapter;
10-
import io.ably.lib.objects.LiveObjectsAdapter;
8+
import io.ably.lib.objects.LiveObjectsHelper;
119
import io.ably.lib.objects.LiveObjectsPlugin;
1210
import io.ably.lib.rest.AblyRest;
1311
import io.ably.lib.rest.Auth;
@@ -74,7 +72,7 @@ public AblyRealtime(ClientOptions options) throws AblyException {
7472
final InternalChannels channels = new InternalChannels();
7573
this.channels = channels;
7674

77-
liveObjectsPlugin = tryInitializeLiveObjectsPlugin();
75+
liveObjectsPlugin = LiveObjectsHelper.tryInitializeLiveObjectsPlugin(this);
7876

7977
connection = new Connection(this, channels, platformAgentProvider, liveObjectsPlugin);
8078

@@ -185,20 +183,6 @@ public interface Channels extends ReadOnlyMap<String, Channel> {
185183
void release(String channelName);
186184
}
187185

188-
private LiveObjectsPlugin tryInitializeLiveObjectsPlugin() {
189-
try {
190-
Class<?> liveObjectsImplementation = Class.forName("io.ably.lib.objects.DefaultLiveObjectsPlugin");
191-
LiveObjectsAdapter adapter = new Adapter(this);
192-
return (LiveObjectsPlugin) liveObjectsImplementation
193-
.getDeclaredConstructor(LiveObjectsAdapter.class)
194-
.newInstance(adapter);
195-
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException |
196-
InvocationTargetException e) {
197-
Log.i(TAG, "LiveObjects plugin not found in classpath. LiveObjects functionality will not be available.", e);
198-
return null;
199-
}
200-
}
201-
202186
private class InternalChannels extends InternalMap<String, Channel> implements Channels, ConnectionManager.Channels {
203187
/**
204188
* Get the named channel; if it does not already exist,

lib/src/main/java/io/ably/lib/types/ProtocolMessage.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
import java.lang.reflect.Type;
55
import java.util.Map;
66

7+
import com.google.gson.annotations.JsonAdapter;
8+
import io.ably.lib.objects.LiveObjectSerializer;
9+
import io.ably.lib.objects.LiveObjectsHelper;
10+
import io.ably.lib.objects.LiveObjectsJsonSerializer;
11+
import org.jetbrains.annotations.Nullable;
712
import org.msgpack.core.MessageFormat;
813
import org.msgpack.core.MessagePacker;
914
import org.msgpack.core.MessageUnpacker;
@@ -123,6 +128,13 @@ public ProtocolMessage(Action action, String channel) {
123128
public AuthDetails auth;
124129
public Map<String, String> params;
125130
public Annotation[] annotations;
131+
/**
132+
* This will be null if we skipped decoding this property due to user not requesting Objects functionality
133+
* JsonAdapter annotation supports java version (1.8) mentioned in build.gradle
134+
*/
135+
@Nullable
136+
@JsonAdapter(LiveObjectsJsonSerializer.class)
137+
public Object[] state;
126138

127139
public boolean hasFlag(final Flag flag) {
128140
return (flags & flag.getMask()) == flag.getMask();
@@ -147,6 +159,7 @@ void writeMsgpack(MessagePacker packer) throws IOException {
147159
if(params != null) ++fieldCount;
148160
if(channelSerial != null) ++fieldCount;
149161
if(annotations != null) ++fieldCount;
162+
if(state != null) ++fieldCount;
150163
packer.packMapHeader(fieldCount);
151164
packer.packString("action");
152165
packer.packInt(action.getValue());
@@ -186,6 +199,15 @@ void writeMsgpack(MessagePacker packer) throws IOException {
186199
packer.packString("annotations");
187200
AnnotationSerializer.writeMsgpackArray(annotations, packer);
188201
}
202+
if(state != null) {
203+
LiveObjectSerializer liveObjectsSerializer = LiveObjectsHelper.getLiveObjectSerializer();
204+
if (liveObjectsSerializer != null) {
205+
packer.packString("state");
206+
liveObjectsSerializer.writeMsgpackArray(state, packer);
207+
} else {
208+
Log.w(TAG, "Skipping 'state' field msgpack serialization because LiveObjectsSerializer not found");
209+
}
210+
}
189211
}
190212

191213
ProtocolMessage readMsgpack(MessageUnpacker unpacker) throws IOException {
@@ -248,6 +270,15 @@ ProtocolMessage readMsgpack(MessageUnpacker unpacker) throws IOException {
248270
case "annotations":
249271
annotations = AnnotationSerializer.readMsgpackArray(unpacker);
250272
break;
273+
case "state":
274+
LiveObjectSerializer liveObjectsSerializer = LiveObjectsHelper.getLiveObjectSerializer();
275+
if (liveObjectsSerializer != null) {
276+
state = liveObjectsSerializer.readMsgpackArray(unpacker);
277+
} else {
278+
Log.w(TAG, "Skipping 'state' field msgpack deserialization because LiveObjectsSerializer not found");
279+
unpacker.skipValue();
280+
}
281+
break;
251282
default:
252283
Log.v(TAG, "Unexpected field: " + fieldName);
253284
unpacker.skipValue();

live-objects/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ repositories {
1111

1212
dependencies {
1313
implementation(project(":java"))
14+
implementation(libs.bundles.common)
1415
implementation(libs.coroutine.core)
1516

1617
testImplementation(kotlin("test"))

live-objects/src/main/kotlin/io/ably/lib/objects/Serialization.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,30 @@ package io.ably.lib.objects
22

33
import com.google.gson.Gson
44
import com.google.gson.GsonBuilder
5+
import com.google.gson.JsonArray
6+
import org.msgpack.core.MessagePacker
7+
import org.msgpack.core.MessageUnpacker
58

69
internal val gson: Gson = createGsonSerializer()
710

811
private fun createGsonSerializer(): Gson {
912
return GsonBuilder().create() // Do not call serializeNulls() to omit null values
1013
}
14+
15+
internal class DefaultLiveObjectSerializer : LiveObjectSerializer {
16+
override fun readMsgpackArray(unpacker: MessageUnpacker): Array<Any> {
17+
TODO("Not yet implemented")
18+
}
19+
20+
override fun writeMsgpackArray(objects: Array<out Any>?, packer: MessagePacker) {
21+
TODO("Not yet implemented")
22+
}
23+
24+
override fun readFromJsonArray(json: JsonArray): Array<Any> {
25+
TODO("Not yet implemented")
26+
}
27+
28+
override fun asJsonArray(objects: Array<out Any>?): JsonArray {
29+
TODO("Not yet implemented")
30+
}
31+
}

0 commit comments

Comments
 (0)