Skip to content

Instantly share code, notes, and snippets.

@ahmedengu
Last active May 15, 2021 21:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ahmedengu/607266b5caeffbc19c4508b4684b4b7b to your computer and use it in GitHub Desktop.
Save ahmedengu/607266b5caeffbc19c4508b4684b4b7b to your computer and use it in GitHub Desktop.
ParseLiveQuery support for codenameone/parse4cn1 require cn1-webSockets , parse4cn1 || LiveQuery setup: https://github.com/ParsePlatform/parse-server/wiki/Parse-LiveQuery#server-setup
package com.parse4cn1;
import ca.weblite.codename1.json.JSONException;
import ca.weblite.codename1.json.JSONObject;
import com.codename1.io.websocket.WebSocket;
import com.codename1.io.websocket.WebSocketState;
import com.parse4cn1.*;
import java.util.HashMap;
import java.util.Map;
/**
* Created by ahmedengu.
*/
public abstract class ParseLiveQuery {
private final static String OP = "op";
private JSONObject query;
private String subscribeData;
private int requestId;
// LiveQuery subscription id
private static int requestIds = 0;
private static String liveQueryServerURL;
private static WebSocket webSocket;
private static Map<Integer, ParseLiveQuery> lQuerys = new HashMap<>();
// optional to handle the websocket events and errors that not related to a specific query
private static WsCallback wsCallback = new WsCallback() {
@Override
public void error(String op, int code, String error, boolean reconnect) {
System.out.println(op + ", code:" + code + ", error:" + error);
}
};
// query callback event
public abstract void event(String op, int requestId, ParseObject object);
// subscribe to a ParseQuery
public ParseLiveQuery(ParseQuery query) throws JSONException, ParseException {
this(query.encode());
}
// subscribe to a JSONObject query
// you should provide classname , where , fields [optional]
private ParseLiveQuery(JSONObject query) throws JSONException, ParseException {
this.query = query;
requestIds++;
requestId = requestIds;
subscribe();
}
// initialzie the websocket connection to the liveQury server
// when it's open its gonna send the connect message
// when it's connected it gonna subscribe to all the available queries [in case there was a disconnection]
//onClose: if it not triggered using .close() it gonna try to reconnect 5 times
private static void init() {
webSocket = new WebSocket(getLiveQueryServerURL()) {
@Override
protected void onOpen() {
this.send(getConnect());
wsCallback.onOpen();
}
@Override
protected void onClose(int i, String s) {
for (int j = 0; s != null && j < 5; j++)
try {
Thread.sleep(3000);
init();
Thread.sleep(1000);
return;
} catch (Exception e1) {
e1.printStackTrace();
}
wsCallback.onClose(i, s);
}
@Override
protected void onMessage(String s) {
try {
JSONObject json = new JSONObject(s);
String operation = json.getString(OP);
switch (operation) {
case "connected":
for (Map.Entry<Integer, ParseLiveQuery> entry : lQuerys.entrySet())
webSocket.send(entry.getValue().getSubscribe());
break;
case "subscribed":
break;
case "unsubscribed":
break;
case "error":
wsCallback.error(operation, json.getInt("code"), json.getString("error"), json.getBoolean("reconnect"));
break;
default:
JSONObject data = json.getJSONObject("object");
ParseObject parseObject = ParseObject.create(data.getString("className"));
parseObject.setData(data);
int requestId = json.getInt("requestId");
lQuerys.get(requestId).event(operation, requestId, parseObject);
break;
}
} catch (JSONException e) {
onError(e);
}
wsCallback.onMessage(s);
}
@Override
protected void onMessage(byte[] bytes) {
wsCallback.onMessage(bytes);
}
@Override
protected void onError(Exception e) {
e.printStackTrace();
wsCallback.onError(e);
}
};
webSocket.connect();
}
//liveQuery connect message
private static String getConnect() {
JSONObject output = new JSONObject();
try {
output.put(OP, "connect");
if (ParseUser.getCurrent() != null)
output.put("sessionToken", ParseUser.getCurrent().getSessionToken());
output.put("clientKey", Parse.getClientKey());
output.put("applicationId", Parse.getApplicationId());
} catch (JSONException ex) {
ex.printStackTrace();
}
return output.toString();
}
//liveQuery subscribe message
private String getSubscribe() throws JSONException {
if (subscribeData == null) {
JSONObject output = new JSONObject();
output.put(OP, "subscribe");
output.put("requestId", this.requestId);
JSONObject q = new JSONObject();
q.put("className", query.getString("className"));
q.put("where", query.get("where"));
if (query.has("keys"))
q.put("fields", query.get("keys"));
if (query.has("fields"))
q.put("fields", query.get("fields"));
output.put("query", q);
if (ParseUser.getCurrent() != null)
output.put("sessionToken", ParseUser.getCurrent().getSessionToken());
subscribeData = output.toString();
}
return subscribeData;
}
//liveQuery unsubscribe message
public void unsubscribe() throws Exception {
unsubscribe(true);
}
public void unsubscribe(boolean remove) throws Exception {
if (remove)
lQuerys.remove(requestId);
JSONObject output = new JSONObject();
output.put(OP, "unsubscribe");
output.put("requestId", requestId);
if (webSocket != null && output != null && (remove || webSocket.getReadyState() == WebSocketState.OPEN))
webSocket.send(output.toString());
}
//subscribe action .. gonna be called in the constructor , but it's public in case there was a disconnection and you want to resubscribe
public void subscribe() throws ParseException, JSONException {
lQuerys.put(requestId, this);
if (webSocket == null)
init();
else if (webSocket.getReadyState() != WebSocketState.OPEN)
webSocket.connect();
else
webSocket.send(getSubscribe());
}
//unsubscribe from all then close the websocket
public static void close() throws Exception {
if (webSocket != null) {
for (Map.Entry<Integer, ParseLiveQuery> entry : lQuerys.entrySet())
entry.getValue().unsubscribe(false);
lQuerys.clear();
requestIds = 0;
webSocket.close();
webSocket = null;
}
}
// LiveQuery server URL from the parse server URL
public static String getLiveQueryServerURL() {
if (liveQueryServerURL == null) {
String endpoint = Parse.getApiEndpoint();
String portcol = (endpoint.indexOf("https") == 0) ? "wss" : "ws";
liveQueryServerURL = portcol + ((endpoint.indexOf("https") == 0) ? endpoint.substring(5) : endpoint.substring(4));
}
return liveQueryServerURL;
}
public int getRequestId() {
return requestId;
}
public JSONObject getQuery() {
return query;
}
public static WsCallback getWsCallback() {
return wsCallback;
}
// optional to handle the websocket events and errors that not related to a specific query
public static void setWsCallback(WsCallback wsCallback) {
ParseLiveQuery.wsCallback = wsCallback;
}
public static abstract class WsCallback {
public abstract void error(String op, int code, String error, boolean reconnect);
public void onOpen() {
}
;
public void onClose(int var1, String var2) {
}
;
public void onMessage(String var1) {
}
;
public void onMessage(byte[] var1) {
}
;
public void onError(Exception var1) {
}
;
}
}
package userclasses;
import ca.weblite.codename1.json.JSONException;
import com.parse4cn1.*;
import generated.StateMachineBase;
import java.util.HashMap;
/**
************
** You can find a simple usage exmple below
************
* Dont forget to checkout livequery setup wiki: https://github.com/ParsePlatform/parse-server/wiki/Parse-LiveQuery#server-setup
* CN1 websockets: https://github.com/shannah/cn1-websockets
* Parse4CN1: https://github.com/sidiabale/parse4cn1
*/
public class StateMachine extends StateMachineBase {
public StateMachine(String resFile) {
super(resFile);
}
protected void initVars(Resources res) {
Parse.initialize("http://localhost:1337/parse", "myAppId", "master");
}
@Override
protected void beforeTest(Form f) {
// optional to handle the websocket events and errors that not related to a specific query
ParseLiveQuery.setWsCallback(new ParseLiveQuery.WsCallback() {
@Override
public void error(String op, int code, String error, boolean reconnect) {
}
@Override
public void onOpen() {
super.onOpen();
}
@Override
public void onClose(int var1, String var2) {
super.onClose(var1, var2);
}
// This method called whenever there's a message in the websocket
@Override
public void onMessage(String var1) {
super.onMessage(var1);
}
@Override
public void onMessage(byte[] var1) {
super.onMessage(var1);
}
@Override
public void onError(Exception var1) {
super.onError(var1);
}
});
try {
// query support $lt, $lte, $gt, $gte, $ne, $in, $nin, $exists, $all, $regex, $nearSphere, $within :: https://github.com/ParsePlatform/parse-server/wiki/Parse-LiveQuery-Protocol-Specification
ParseQuery<ParseObject> query = ParseQuery.getQuery("GameScore");
query.whereEqualTo("playerName", "Sean Plott").whereEqualTo("cheatMode", false);
// subscribe
ParseLiveQuery liveQuery = new ParseLiveQuery(query) {
@Override
// this method called when there is an event
// https://github.com/ParsePlatform/parse-server/wiki/Parse-LiveQuery#events
public void event(String op, int requestId, ParseObject object) {
System.out.println(op + " " + object.getObjectId());
}
};
// unsubscribe from a spacific query
liveQuery.unsubscribe();
// close the Websocket and unsubscribe from all queries
ParseLiveQuery.close();
} catch (JSONException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}
}
@blessingmobile
Copy link

Good day Ahmed,

Many thanks for this API integration. The example code is great, however I spent some time getting it to work not realising that I need to register the Classes in order for Live Query to work in the index.js Parse Server file. Like so:

liveQuery: {
classNames: ["Posts", "Comments", "GameScore"] // List of classes to support for query subscriptions
},

Can you make a note of this somewhere maybe in the example and also mention that the client side "onMessage()" callback is called every time there is an update from the server.

@ahmedengu
Copy link
Author

Greetings @blessingmobile,

Thank you for your feedback it's really appreciated and sorry for my late reply.
I have included a link to liveQuery setup and added some comments to the code as you suggested hopefully it's more clear now.
Kindly, feel free to comment any improvements or to fork the snippet.

Best Regards,
Ahmed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment