Skip to content

Commit 5c694c9

Browse files
committed
[ECO-5386] Added interfaces for liveobject serialization
1. Added LiveObjectSerializer interface consisting methods for json and msgpack serialization. 2. Added LiveObjectsHelper to initialize LiveObjectsPlugin and LiveObjectSerializer 3. Updated code to use LiveObjectSerializer and LiveObjectsHelper to serialize and initialize liveobjects
1 parent 80a655e commit 5c694c9

6 files changed

Lines changed: 174 additions & 25 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: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 volatile 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+
synchronized (LiveObjectsHelper.class) {
30+
try {
31+
Class<?> serializerClass = Class.forName("io.ably.lib.objects.serialization.DefaultLiveObjectSerializer");
32+
liveObjectSerializer = (LiveObjectSerializer) serializerClass.getDeclaredConstructor().newInstance();
33+
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException |
34+
NoSuchMethodException |
35+
InvocationTargetException e) {
36+
Log.e(TAG, "Failed to init LiveObjectSerializer, LiveObjects plugin not included in the classpath", e);
37+
return null;
38+
}
39+
}
40+
}
41+
return liveObjectSerializer;
42+
}
43+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.ably.lib.objects;
2+
3+
import com.google.gson.JsonDeserializationContext;
4+
import com.google.gson.JsonDeserializer;
5+
import com.google.gson.JsonElement;
6+
import com.google.gson.JsonNull;
7+
import com.google.gson.JsonParseException;
8+
import com.google.gson.JsonSerializationContext;
9+
import com.google.gson.JsonSerializer;
10+
import io.ably.lib.util.Log;
11+
12+
import java.lang.reflect.Type;
13+
14+
public class LiveObjectsJsonSerializer implements JsonSerializer<Object[]>, JsonDeserializer<Object[]> {
15+
private static final String TAG = LiveObjectsJsonSerializer.class.getName();
16+
private final LiveObjectSerializer serializer = LiveObjectsHelper.getLiveObjectSerializer();
17+
18+
@Override
19+
public Object[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
20+
if (serializer == null) {
21+
Log.w(TAG, "Skipping 'state' field json deserialization because LiveObjectsSerializer not found.");
22+
return null;
23+
}
24+
if (!json.isJsonArray()) {
25+
throw new JsonParseException("Expected a JSON array for 'state' field, but got: " + json);
26+
}
27+
return serializer.readFromJsonArray(json.getAsJsonArray());
28+
}
29+
30+
@Override
31+
public JsonElement serialize(Object[] src, Type typeOfSrc, JsonSerializationContext context) {
32+
if (serializer == null) {
33+
Log.w(TAG, "Skipping 'state' field json serialization because LiveObjectsSerializer not found.");
34+
return JsonNull.INSTANCE;
35+
}
36+
return serializer.asJsonArray(src);
37+
}
38+
}

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: 32 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,14 @@ 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+
* This is targeted and specific to the state field, so won't affect other fields
135+
*/
136+
@Nullable
137+
@JsonAdapter(LiveObjectsJsonSerializer.class)
138+
public Object[] state;
126139

127140
public boolean hasFlag(final Flag flag) {
128141
return (flags & flag.getMask()) == flag.getMask();
@@ -147,6 +160,7 @@ void writeMsgpack(MessagePacker packer) throws IOException {
147160
if(params != null) ++fieldCount;
148161
if(channelSerial != null) ++fieldCount;
149162
if(annotations != null) ++fieldCount;
163+
if(state != null && LiveObjectsHelper.getLiveObjectSerializer() != null) ++fieldCount;
150164
packer.packMapHeader(fieldCount);
151165
packer.packString("action");
152166
packer.packInt(action.getValue());
@@ -186,6 +200,15 @@ void writeMsgpack(MessagePacker packer) throws IOException {
186200
packer.packString("annotations");
187201
AnnotationSerializer.writeMsgpackArray(annotations, packer);
188202
}
203+
if(state != null) {
204+
LiveObjectSerializer liveObjectsSerializer = LiveObjectsHelper.getLiveObjectSerializer();
205+
if (liveObjectsSerializer != null) {
206+
packer.packString("state");
207+
liveObjectsSerializer.writeMsgpackArray(state, packer);
208+
} else {
209+
Log.w(TAG, "Skipping 'state' field msgpack serialization because LiveObjectsSerializer not found");
210+
}
211+
}
189212
}
190213

191214
ProtocolMessage readMsgpack(MessageUnpacker unpacker) throws IOException {
@@ -248,6 +271,15 @@ ProtocolMessage readMsgpack(MessageUnpacker unpacker) throws IOException {
248271
case "annotations":
249272
annotations = AnnotationSerializer.readMsgpackArray(unpacker);
250273
break;
274+
case "state":
275+
LiveObjectSerializer liveObjectsSerializer = LiveObjectsHelper.getLiveObjectSerializer();
276+
if (liveObjectsSerializer != null) {
277+
state = liveObjectsSerializer.readMsgpackArray(unpacker);
278+
} else {
279+
Log.w(TAG, "Skipping 'state' field msgpack deserialization because LiveObjectsSerializer not found");
280+
unpacker.skipValue();
281+
}
282+
break;
251283
default:
252284
Log.v(TAG, "Unexpected field: " + fieldName);
253285
unpacker.skipValue();

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class ProtocolSerializer {
1414
/****************************************
1515
* Msgpack decode
1616
****************************************/
17-
17+
1818
public static ProtocolMessage readMsgpack(byte[] packed) throws AblyException {
1919
try {
2020
MessageUnpacker unpacker = Serialisation.msgpackUnpackerConfig.newUnpacker(packed);
@@ -27,30 +27,31 @@ public static ProtocolMessage readMsgpack(byte[] packed) throws AblyException {
2727
/****************************************
2828
* Msgpack encode
2929
****************************************/
30-
31-
public static byte[] writeMsgpack(ProtocolMessage message) {
30+
31+
public static byte[] writeMsgpack(ProtocolMessage message) throws AblyException {
3232
ByteArrayOutputStream out = new ByteArrayOutputStream();
3333
MessagePacker packer = Serialisation.msgpackPackerConfig.newPacker(out);
3434
try {
3535
message.writeMsgpack(packer);
36-
3736
packer.flush();
3837
return out.toByteArray();
39-
} catch(IOException e) { return null; }
38+
} catch (IOException ioe) {
39+
throw AblyException.fromThrowable(ioe);
40+
}
4041
}
4142

4243
/****************************************
4344
* JSON decode
4445
****************************************/
45-
46+
4647
public static ProtocolMessage fromJSON(String packed) throws AblyException {
4748
return Serialisation.gson.fromJson(packed, ProtocolMessage.class);
4849
}
4950

5051
/****************************************
5152
* JSON encode
5253
****************************************/
53-
54+
5455
public static byte[] writeJSON(ProtocolMessage message) throws AblyException {
5556
return Serialisation.gson.toJson(message).getBytes(Charset.forName("UTF-8"));
5657
}

0 commit comments

Comments
 (0)