Skip to content

Commit 909109d

Browse files
committed
Added gson serializers to liveobjects module, declared common protomsg state
field between common to liveobjects and core java sdk
1 parent 375328e commit 909109d

File tree

7 files changed

+155
-15
lines changed

7 files changed

+155
-15
lines changed

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

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

7-
import org.msgpack.core.MessageFormat;
8-
import org.msgpack.core.MessagePacker;
9-
import org.msgpack.core.MessageUnpacker;
10-
7+
import com.google.gson.JsonArray;
118
import com.google.gson.JsonDeserializationContext;
129
import com.google.gson.JsonDeserializer;
1310
import com.google.gson.JsonElement;
1411
import com.google.gson.JsonParseException;
1512
import com.google.gson.JsonPrimitive;
1613
import com.google.gson.JsonSerializationContext;
1714
import com.google.gson.JsonSerializer;
15+
import org.jetbrains.annotations.Nullable;
16+
import org.msgpack.core.MessageFormat;
17+
import org.msgpack.core.MessagePacker;
18+
import org.msgpack.core.MessageUnpacker;
1819

1920
import io.ably.lib.util.Log;
2021

22+
import static io.ably.lib.util.Serialisation.gsonToMsgpack;
23+
import static io.ably.lib.util.Serialisation.msgpackToGson;
24+
2125
/**
2226
* A message sent and received over the Realtime protocol.
2327
* A ProtocolMessage always relates to a single channel only, but
@@ -116,6 +120,11 @@ public ProtocolMessage(Action action, String channel) {
116120
public ConnectionDetails connectionDetails;
117121
public AuthDetails auth;
118122
public Map<String, String> params;
123+
/**
124+
* This will be null if we skipped decoding this property due to user not requesting Objects functionality
125+
*/
126+
public @Nullable JsonArray state;
127+
119128

120129
public boolean hasFlag(final Flag flag) {
121130
return (flags & flag.getMask()) == flag.getMask();
@@ -139,6 +148,7 @@ void writeMsgpack(MessagePacker packer) throws IOException {
139148
if(flags != 0) ++fieldCount;
140149
if(params != null) ++fieldCount;
141150
if(channelSerial != null) ++fieldCount;
151+
if(state != null) ++fieldCount;
142152
packer.packMapHeader(fieldCount);
143153
packer.packString("action");
144154
packer.packInt(action.getValue());
@@ -174,6 +184,10 @@ void writeMsgpack(MessagePacker packer) throws IOException {
174184
packer.packString("channelSerial");
175185
packer.packString(channelSerial);
176186
}
187+
if(state != null) {
188+
packer.packString("state");
189+
gsonToMsgpack(state, packer);
190+
}
177191
}
178192

179193
ProtocolMessage readMsgpack(MessageUnpacker unpacker) throws IOException {
@@ -233,6 +247,9 @@ ProtocolMessage readMsgpack(MessageUnpacker unpacker) throws IOException {
233247
case "params":
234248
params = MessageSerializer.readStringMap(unpacker);
235249
break;
250+
case "state":
251+
state = (JsonArray) msgpackToGson(unpacker.unpackValue());
252+
break;
236253
default:
237254
Log.v(TAG, "Unexpected field: " + fieldName);
238255
unpacker.skipValue();

lib/src/main/java/io/ably/lib/util/Base64Coder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,4 @@ public static byte[] decode (char[] in, int iOff, int iLen) {
236236
//Dummy constructor.
237237
private Base64Coder() {}
238238

239-
} // end class Base64Coder
239+
} // end class Base64Coder

lib/src/main/java/io/ably/lib/util/Serialisation.java

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import com.google.gson.JsonObject;
99
import com.google.gson.JsonParser;
1010
import com.google.gson.JsonPrimitive;
11+
import com.google.gson.JsonSerializationContext;
12+
import com.google.gson.JsonSerializer;
1113
import io.ably.lib.http.HttpCore;
1214
import io.ably.lib.platform.Platform;
1315
import io.ably.lib.types.AblyException;
@@ -27,12 +29,14 @@
2729
import java.io.IOException;
2830
import java.io.UnsupportedEncodingException;
2931
import java.lang.reflect.Array;
32+
import java.lang.reflect.Type;
3033
import java.math.BigDecimal;
3134
import java.math.BigInteger;
3235
import java.util.Map;
3336
import java.util.Set;
3437

3538
public class Serialisation {
39+
public static final String TAG = Serialisation.class.getName();
3640
public static final JsonParser gsonParser;
3741
public static final GsonBuilder gsonBuilder;
3842
public static final Gson gson;
@@ -48,6 +52,7 @@ public class Serialisation {
4852
gsonBuilder.registerTypeAdapter(PresenceMessage.class, new PresenceMessage.Serializer());
4953
gsonBuilder.registerTypeAdapter(PresenceMessage.Action.class, new PresenceMessage.ActionSerializer());
5054
gsonBuilder.registerTypeAdapter(ProtocolMessage.Action.class, new ProtocolMessage.ActionSerializer());
55+
gsonBuilder.registerTypeAdapter(Base64EncodedJsonPrimitive.class, new Base64EncodedJsonPrimitive.Serializer());
5156
gson = gsonBuilder.create();
5257

5358
msgpackPackerConfig = Platform.name.equals("android") ?
@@ -193,18 +198,36 @@ public static void gsonToMsgpack(JsonElement json, MessagePacker packer) {
193198
gsonToMsgpack((JsonNull)json, packer);
194199
} else if (json.isJsonPrimitive()) {
195200
gsonToMsgpack((JsonPrimitive)json, packer);
196-
} else {
201+
} else if (json instanceof Base64EncodedJsonPrimitive) {
202+
gsonToMsgpack((Base64EncodedJsonPrimitive)json, packer);
203+
}
204+
else {
205+
Log.e(TAG, "Unsupported JsonElement type: " + json.getClass().getName());
197206
throw new RuntimeException("unreachable");
198207
}
199208
}
200209

210+
public static void gsonToMsgpack(Base64EncodedJsonPrimitive json, MessagePacker packer) {
211+
try {
212+
String value = json.getAsString();
213+
byte[] decodedData = Base64Coder.decode(value);
214+
packer.packBinaryHeader(decodedData.length);
215+
packer.writePayload(decodedData);
216+
} catch (IOException e) {
217+
throw new RuntimeException(e);
218+
}
219+
}
220+
201221
private static void gsonToMsgpack(JsonArray array, MessagePacker packer) {
202222
try {
203223
packer.packArrayHeader(array.size());
204224
for (JsonElement elem : array) {
205225
gsonToMsgpack(elem, packer);
206226
}
207-
} catch(IOException e) {}
227+
} catch(IOException e) {
228+
// Handle IOException, possibly log it or rethrow as a runtime exception
229+
Log.e(TAG, "Error packing JsonArray to MsgPack", e);
230+
}
208231
}
209232

210233
private static void gsonToMsgpack(JsonObject object, MessagePacker packer) {
@@ -215,13 +238,17 @@ private static void gsonToMsgpack(JsonObject object, MessagePacker packer) {
215238
packer.packString(entry.getKey());
216239
gsonToMsgpack(entry.getValue(), packer);
217240
}
218-
} catch(IOException e) {}
241+
} catch(IOException e) {
242+
Log.e(TAG, "Error packing JsonObject to MsgPack", e);
243+
}
219244
}
220245

221246
private static void gsonToMsgpack(JsonNull n, MessagePacker packer) {
222247
try {
223248
packer.packNil();
224-
} catch(IOException e) {}
249+
} catch(IOException e) {
250+
Log.e(TAG, "Error packing JsonNull to MsgPack", e);
251+
}
225252
}
226253

227254
private static void gsonToMsgpack(JsonPrimitive primitive, MessagePacker packer) {
@@ -248,7 +275,9 @@ private static void gsonToMsgpack(JsonPrimitive primitive, MessagePacker packer)
248275
} else {
249276
packer.packString(primitive.getAsString());
250277
}
251-
} catch(IOException e) {}
278+
} catch(Exception e) {
279+
Log.e(TAG, "Error packing JsonPrimitive to MsgPack", e);
280+
}
252281
}
253282

254283
public static JsonElement msgpackToGson(Value value) {
@@ -286,4 +315,29 @@ public static JsonElement msgpackToGson(Value value) {
286315
return null;
287316
}
288317
}
318+
319+
public static class Base64EncodedJsonPrimitive extends JsonElement {
320+
private final String value;
321+
322+
public Base64EncodedJsonPrimitive(String value) {
323+
this.value = value;
324+
}
325+
326+
@Override
327+
public String getAsString() {
328+
return value;
329+
}
330+
331+
@Override
332+
public JsonElement deepCopy() {
333+
return null;
334+
}
335+
336+
public static class Serializer implements JsonSerializer<Base64EncodedJsonPrimitive> {
337+
@Override
338+
public JsonElement serialize(Base64EncodedJsonPrimitive src, Type typeOfSrc, JsonSerializationContext context) {
339+
return new JsonPrimitive(src.getAsString());
340+
}
341+
}
342+
}
289343
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.ably.lib.objects
22

3+
import com.google.gson.JsonArray
34
import io.ably.lib.types.Callback
45
import io.ably.lib.types.ProtocolMessage
56
import io.ably.lib.util.Log
@@ -55,6 +56,19 @@ internal class DefaultLiveObjects(private val channelName: String, private val a
5556
adapter.setChannelSerial(channelName, msg.channelSerial)
5657
}
5758
}
59+
val objectMessages = msg.state?.map { it.toObjectMessage() } ?: emptyList()
60+
Log.v(tag, "Received ${objectMessages.size} object messages for channelName: $channelName")
61+
objectMessages.forEach { Log.v(tag, "Object message: $it") }
62+
}
63+
64+
suspend fun send(message: ObjectMessage) {
65+
Log.v(tag, "Sending message for channelName: $channelName, message: $message")
66+
val protocolMsg = ProtocolMessage().apply {
67+
state = JsonArray().apply {
68+
add(message.toJsonObject())
69+
}
70+
}
71+
adapter.sendAsync(protocolMsg)
5872
}
5973

6074
fun dispose() {

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,21 @@ internal suspend fun LiveObjectsAdapter.sendAsync(message: ProtocolMessage) {
2323
deferred.await()
2424
}
2525

26-
internal enum class MessageFormat(private val value: String) {
26+
internal enum class ProtocolMessageFormat(private val value: String) {
2727
MSGPACK("msgpack"),
2828
JSON("json");
2929

3030
override fun toString(): String = value
3131
}
32+
33+
internal class Binary(val data: ByteArray?) {
34+
override fun equals(other: Any?): Boolean {
35+
if (this === other) return true
36+
if (other !is Binary) return false
37+
return data?.contentEquals(other.data) == true
38+
}
39+
40+
override fun hashCode(): Int {
41+
return data?.contentHashCode() ?: 0
42+
}
43+
}

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package io.ably.lib.objects
22

3-
import java.nio.ByteBuffer
4-
53
/**
64
* An enum class representing the different actions that can be performed on an object.
75
* Spec: OOP2
@@ -190,12 +188,12 @@ internal data class ObjectOperation(
190188
* the initialValue, nonce, and initialValueEncoding will be removed.
191189
* Spec: OOP3h
192190
*/
193-
val initialValue: ByteBuffer? = null,
191+
val initialValue: Binary? = null,
194192

195193
/** The initial value encoding defines how the initialValue should be interpreted.
196194
* Spec: OOP3i
197195
*/
198-
val initialValueEncoding: MessageFormat? = null
196+
val initialValueEncoding: ProtocolMessageFormat? = null
199197
)
200198

201199
/**
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.ably.lib.objects
2+
3+
import com.google.gson.*
4+
import io.ably.lib.util.Base64Coder
5+
import io.ably.lib.util.Serialisation.Base64EncodedJsonPrimitive
6+
import java.lang.reflect.Type
7+
8+
/**
9+
* Creates a Gson instance with a custom serializer for live objects.
10+
* Omits null values during serialization.
11+
*/
12+
13+
internal fun ObjectMessage.toJsonObject(): JsonObject {
14+
return gson.toJsonTree(this).asJsonObject
15+
}
16+
17+
internal fun JsonElement.toObjectMessage(): ObjectMessage {
18+
return gson.fromJson(this, ObjectMessage::class.java)
19+
}
20+
21+
private val gson: Gson = createGsonSerializer()
22+
23+
private fun createGsonSerializer(): Gson {
24+
return GsonBuilder()
25+
.registerTypeAdapter(Binary::class.java, BinarySerializer())
26+
.create() // Do not call serializeNulls() to omit null values
27+
}
28+
29+
// Custom serializer for Binary type
30+
internal class BinarySerializer : JsonSerializer<Binary>, JsonDeserializer<Binary> {
31+
override fun serialize(src: Binary?, typeOfSrc: Type, context: JsonSerializationContext): JsonElement? {
32+
src?.data?.let {
33+
return Base64EncodedJsonPrimitive(Base64Coder.encodeToString(it))
34+
}
35+
return null // Omit null values
36+
}
37+
38+
override fun deserialize(json: JsonElement?, typeOfT: Type, context: JsonDeserializationContext): Binary? {
39+
if (json != null && json.isJsonPrimitive) {
40+
val decodedData = Base64Coder.decode(json.asString)
41+
return Binary(decodedData)
42+
}
43+
return null // Return null if the JSON element is not valid
44+
}
45+
}

0 commit comments

Comments
 (0)