Skip to content

Commit a46f753

Browse files
committed
[ECO-5642] feat: add message editing support to channels
- Introduced APIs to retrieve, update, and delete messages by serial identifier. - Added support for fetching historical versions of messages. - Implemented `MessageEditsMixin` to handle message editing operations. - Added tests for message editing and retrieval functionality.
1 parent 4f354a8 commit a46f753

4 files changed

Lines changed: 1018 additions & 3 deletions

File tree

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

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.ably.lib.http.HttpUtils;
1717
import io.ably.lib.objects.RealtimeObjects;
1818
import io.ably.lib.objects.LiveObjectsPlugin;
19+
import io.ably.lib.rest.MessageEditsMixin;
1920
import io.ably.lib.rest.RestAnnotations;
2021
import io.ably.lib.transport.ConnectionManager;
2122
import io.ably.lib.transport.ConnectionManager.QueuedMessage;
@@ -100,6 +101,8 @@ public abstract class ChannelBase extends EventEmitter<ChannelEvent, ChannelStat
100101

101102
@Nullable private final LiveObjectsPlugin liveObjectsPlugin;
102103

104+
private final MessageEditsMixin messageEditsMixin;
105+
103106
public RealtimeObjects getObjects() throws AblyException {
104107
if (liveObjectsPlugin == null) {
105108
throw AblyException.fromErrorInfo(
@@ -1172,6 +1175,121 @@ else if(!"false".equalsIgnoreCase(param.value)) {
11721175
private static final String KEY_UNTIL_ATTACH = "untilAttach";
11731176
private static final String KEY_FROM_SERIAL = "fromSerial";
11741177

1178+
//region Message Edits and Deletes
1179+
1180+
/**
1181+
* Retrieves the latest version of a specific message by its serial identifier.
1182+
* <p>
1183+
* This method allows you to fetch the current state of a message, including any updates
1184+
* or deletions that have been applied since its creation.
1185+
*
1186+
* @param serial The unique serial identifier of the message to retrieve.
1187+
* @return A {@link Message} object representing the latest version of the message.
1188+
* @throws AblyException If the message cannot be retrieved or does not exist.
1189+
*/
1190+
public Message getMessage(String serial) throws AblyException {
1191+
return messageEditsMixin.getMessage(ably.http, serial);
1192+
}
1193+
1194+
/**
1195+
* Asynchronously retrieves the latest version of a specific message by its serial identifier.
1196+
*
1197+
* @param serial The unique serial identifier of the message to retrieve.
1198+
* @param callback A callback to handle the result asynchronously.
1199+
* <p>
1200+
* This callback is invoked on a background thread.
1201+
*/
1202+
public void getMessageAsync(String serial, Callback<Message> callback) {
1203+
messageEditsMixin.getMessageAsync(ably.http, serial, callback);
1204+
}
1205+
1206+
/**
1207+
* Updates an existing message by its serial identifier using patch semantics.
1208+
* <p>
1209+
* Non-null fields in the provided message (name, data, extras) will replace the corresponding
1210+
* fields in the existing message, while null fields will be left unchanged.
1211+
*
1212+
* @param serial The unique serial identifier of the message to update.
1213+
* @param message A {@link Message} object containing the fields to update.
1214+
* Only non-null fields will be applied to the existing message.
1215+
* @throws AblyException If the update operation fails.
1216+
*/
1217+
public void updateMessage(String serial, Message message) throws AblyException {
1218+
messageEditsMixin.updateMessage(ably.http, serial, message);
1219+
}
1220+
1221+
/**
1222+
* Asynchronously updates an existing message by its serial identifier.
1223+
*
1224+
* @param serial The unique serial identifier of the message to update.
1225+
* @param message A {@link Message} object containing the fields to update.
1226+
* @param listener A listener to be notified of the outcome of this operation.
1227+
* <p>
1228+
* This listener is invoked on a background thread.
1229+
*/
1230+
public void updateMessageAsync(String serial, Message message, CompletionListener listener) {
1231+
messageEditsMixin.updateMessageAsync(ably.http, serial, message, listener);
1232+
}
1233+
1234+
/**
1235+
* Marks a message as deleted by its serial identifier.
1236+
* <p>
1237+
* This operation does not remove the message from history; it marks it as deleted
1238+
* while preserving the full message history. The deleted message can still be
1239+
* retrieved and will have its action set to MESSAGE_DELETE.
1240+
*
1241+
* @param serial The unique serial identifier of the message to delete.
1242+
* @param message A {@link Message} object that may contain operation metadata such as
1243+
* clientId, description, or metadata in the version field.
1244+
* @throws AblyException If the delete operation fails.
1245+
*/
1246+
public void deleteMessage(String serial, Message message) throws AblyException {
1247+
messageEditsMixin.deleteMessage(ably.http, serial, message);
1248+
}
1249+
1250+
/**
1251+
* Asynchronously marks a message as deleted by its serial identifier.
1252+
*
1253+
* @param serial The unique serial identifier of the message to delete.
1254+
* @param message A {@link Message} object for operation metadata.
1255+
* @param listener A listener to be notified of the outcome of this operation.
1256+
* <p>
1257+
* This listener is invoked on a background thread.
1258+
*/
1259+
public void deleteMessageAsync(String serial, Message message, CompletionListener listener) {
1260+
messageEditsMixin.deleteMessageAsync(ably.http, serial, message, listener);
1261+
}
1262+
1263+
/**
1264+
* Retrieves all historical versions of a specific message.
1265+
* <p>
1266+
* This method returns a paginated result containing all versions of the message,
1267+
* ordered chronologically. Each version includes metadata about when and by whom
1268+
* the message was modified.
1269+
*
1270+
* @param serial The unique serial identifier of the message.
1271+
* @param params Query parameters for filtering or pagination (e.g., limit, start, end).
1272+
* @return A {@link PaginatedResult} containing an array of {@link Message} objects
1273+
* representing all versions of the message.
1274+
* @throws AblyException If the versions cannot be retrieved.
1275+
*/
1276+
public PaginatedResult<Message> getMessageVersions(String serial, Param[] params) throws AblyException {
1277+
return messageEditsMixin.getMessageVersions(ably.http, serial, params);
1278+
}
1279+
1280+
/**
1281+
* Asynchronously retrieves all historical versions of a specific message.
1282+
*
1283+
* @param serial The unique serial identifier of the message.
1284+
* @param params Query parameters for filtering or pagination.
1285+
* @param callback A callback to handle the result asynchronously.
1286+
*/
1287+
public void getMessageVersionsAsync(String serial, Param[] params, Callback<AsyncPaginatedResult<Message>> callback) throws AblyException {
1288+
messageEditsMixin.getMessageVersionsAsync(ably.http, serial, params, callback);
1289+
}
1290+
1291+
//endregion
1292+
11751293
/************************************
11761294
* Channel history
11771295
************************************/
@@ -1353,6 +1471,7 @@ else if(stateChange.current.equals(failureState)) {
13531471
this,
13541472
new RestAnnotations(name, ably.http, ably.options, options)
13551473
);
1474+
this.messageEditsMixin = new MessageEditsMixin(basePath, ably.options, options, ably.auth);
13561475
}
13571476

13581477
void onChannelMessage(ProtocolMessage msg) {

lib/src/main/java/io/ably/lib/rest/ChannelBase.java

Lines changed: 121 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@
1010
import io.ably.lib.types.AsyncPaginatedResult;
1111
import io.ably.lib.types.Callback;
1212
import io.ably.lib.types.ChannelOptions;
13+
import io.ably.lib.types.ErrorInfo;
1314
import io.ably.lib.types.Message;
15+
import io.ably.lib.types.MessageAction;
16+
import io.ably.lib.types.MessageDecodeException;
1417
import io.ably.lib.types.MessageSerializer;
1518
import io.ably.lib.types.PaginatedResult;
1619
import io.ably.lib.types.Param;
1720
import io.ably.lib.types.PresenceMessage;
1821
import io.ably.lib.types.PresenceSerializer;
1922
import io.ably.lib.util.Crypto;
23+
import io.ably.lib.util.Serialisation;
2024

2125
/**
2226
* A class representing a Channel in the Ably REST API.
@@ -45,13 +49,15 @@ public class ChannelBase {
4549
public final RestAnnotations annotations;
4650

4751

52+
private final MessageEditsMixin messageEditsMixin;
53+
54+
4855
/**
4956
* Publish a message on this channel using the REST API.
5057
* Since the REST API is stateless, this request is made independently
5158
* of any other request on this or any other channel.
5259
* @param name the event name
53-
* @param data the message payload; see {@link io.ably.types.Data} for
54-
* details of supported data types.
60+
* @param data the message payload;
5561
* @throws AblyException
5662
*/
5763
public void publish(String name, Object data) throws AblyException {
@@ -68,7 +74,7 @@ void publish(Http http, String name, Object data) throws AblyException {
6874
* of any other request on this or any other channel.
6975
*
7076
* @param name the event name
71-
* @param data the message payload; see {@link io.ably.types.Data} for
77+
* @param data the message payload;
7278
* @param listener a listener to be notified of the outcome of this message.
7379
* <p>
7480
* This listener is invoked on a background thread.
@@ -311,6 +317,117 @@ private BasePaginatedQuery.ResultRequest<PresenceMessage> historyImpl(Http http,
311317

312318
}
313319

320+
/**
321+
* Retrieves the latest version of a specific message by its serial identifier.
322+
* <p>
323+
* This method allows you to fetch the current state of a message, including any updates
324+
* or deletions that have been applied since its creation.
325+
*
326+
* @param serial The unique serial identifier of the message to retrieve.
327+
* @return A {@link Message} object representing the latest version of the message.
328+
* @throws AblyException If the message cannot be retrieved or does not exist.
329+
*/
330+
public Message getMessage(String serial) throws AblyException {
331+
return messageEditsMixin.getMessage(ably.http, serial);
332+
}
333+
334+
/**
335+
* Asynchronously retrieves the latest version of a specific message by its serial identifier.
336+
*
337+
* @param serial The unique serial identifier of the message to retrieve.
338+
* @param callback A callback to handle the result asynchronously.
339+
* <p>
340+
* This callback is invoked on a background thread.
341+
*/
342+
public void getMessageAsync(String serial, Callback<Message> callback) {
343+
messageEditsMixin.getMessageAsync(ably.http, serial, callback);
344+
}
345+
346+
/**
347+
* Updates an existing message by its serial identifier using patch semantics.
348+
* <p>
349+
* Non-null fields in the provided message (name, data, extras) will replace the corresponding
350+
* fields in the existing message, while null fields will be left unchanged.
351+
*
352+
* @param serial The unique serial identifier of the message to update.
353+
* @param message A {@link Message} object containing the fields to update.
354+
* Only non-null fields will be applied to the existing message.
355+
* @throws AblyException If the update operation fails.
356+
*/
357+
public void updateMessage(String serial, Message message) throws AblyException {
358+
messageEditsMixin.updateMessage(ably.http, serial, message);
359+
}
360+
361+
/**
362+
* Asynchronously updates an existing message by its serial identifier.
363+
*
364+
* @param serial The unique serial identifier of the message to update.
365+
* @param message A {@link Message} object containing the fields to update.
366+
* @param listener A listener to be notified of the outcome of this operation.
367+
* <p>
368+
* This listener is invoked on a background thread.
369+
*/
370+
public void updateMessageAsync(String serial, Message message, CompletionListener listener) {
371+
messageEditsMixin.updateMessageAsync(ably.http, serial, message, listener);
372+
}
373+
374+
/**
375+
* Marks a message as deleted by its serial identifier.
376+
* <p>
377+
* This operation does not remove the message from history; it marks it as deleted
378+
* while preserving the full message history. The deleted message can still be
379+
* retrieved and will have its action set to MESSAGE_DELETE.
380+
*
381+
* @param serial The unique serial identifier of the message to delete.
382+
* @param message A {@link Message} object that may contain operation metadata such as
383+
* clientId, description, or metadata in the version field.
384+
* @throws AblyException If the delete operation fails.
385+
*/
386+
public void deleteMessage(String serial, Message message) throws AblyException {
387+
messageEditsMixin.deleteMessage(ably.http, serial, message);
388+
}
389+
390+
/**
391+
* Asynchronously marks a message as deleted by its serial identifier.
392+
*
393+
* @param serial The unique serial identifier of the message to delete.
394+
* @param message A {@link Message} object for operation metadata.
395+
* @param listener A listener to be notified of the outcome of this operation.
396+
* <p>
397+
* This listener is invoked on a background thread.
398+
*/
399+
public void deleteMessageAsync(String serial, Message message, CompletionListener listener) {
400+
messageEditsMixin.deleteMessageAsync(ably.http, serial, message, listener);
401+
}
402+
403+
/**
404+
* Retrieves all historical versions of a specific message.
405+
* <p>
406+
* This method returns a paginated result containing all versions of the message,
407+
* ordered chronologically. Each version includes metadata about when and by whom
408+
* the message was modified.
409+
*
410+
* @param serial The unique serial identifier of the message.
411+
* @param params Query parameters for filtering or pagination (e.g., limit, start, end).
412+
* @return A {@link PaginatedResult} containing an array of {@link Message} objects
413+
* representing all versions of the message.
414+
* @throws AblyException If the versions cannot be retrieved.
415+
*/
416+
public PaginatedResult<Message> getMessageVersions(String serial, Param[] params) throws AblyException {
417+
return messageEditsMixin.getMessageVersions(ably.http, serial, params);
418+
}
419+
420+
/**
421+
* Asynchronously retrieves all historical versions of a specific message.
422+
*
423+
* @param serial The unique serial identifier of the message.
424+
* @param params Query parameters for filtering or pagination.
425+
* @param callback A callback to handle the result asynchronously.
426+
*/
427+
public void getMessageVersionsAsync(String serial, Param[] params, Callback<AsyncPaginatedResult<Message>> callback) throws AblyException {
428+
messageEditsMixin.getMessageVersionsAsync(ably.http, serial, params, callback);
429+
}
430+
314431
/******************
315432
* internal
316433
* @throws AblyException
@@ -323,6 +440,7 @@ private BasePaginatedQuery.ResultRequest<PresenceMessage> historyImpl(Http http,
323440
this.basePath = "/channels/" + HttpUtils.encodeURIComponent(name);
324441
this.presence = new Presence();
325442
this.annotations = new RestAnnotations(name, ably.http, ably.options, options);
443+
this.messageEditsMixin = new MessageEditsMixin(basePath, ably.options, options, ably.auth);
326444
}
327445

328446
private final AblyBase ably;

0 commit comments

Comments
 (0)