Base Configuration
This commit is contained in:
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2015-present, Parse, LLC.
|
||||
~ All rights reserved.
|
||||
~
|
||||
~ This source code is licensed under the BSD-style license found in the
|
||||
~ LICENSE file in the root directory of this source tree. An additional grant
|
||||
~ of patent rights can be found in the PATENTS file in the same directory.
|
||||
-->
|
||||
<manifest package="com.parse.livequery">
|
||||
<application />
|
||||
</manifest>
|
@ -0,0 +1,10 @@
|
||||
package com.parse;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/* package */ abstract class ClientOperation {
|
||||
|
||||
abstract JSONObject getJSONObjectRepresentation() throws JSONException;
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.parse;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/* package */ class ConnectClientOperation extends ClientOperation {
|
||||
|
||||
private final String applicationId;
|
||||
private final String sessionToken;
|
||||
|
||||
/* package */ ConnectClientOperation(String applicationId, String sessionToken) {
|
||||
this.applicationId = applicationId;
|
||||
this.sessionToken = sessionToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ JSONObject getJSONObjectRepresentation() throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("op", "connect");
|
||||
jsonObject.put("applicationId", applicationId);
|
||||
jsonObject.put("sessionToken", sessionToken);
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package com.parse;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public abstract class LiveQueryException extends Exception {
|
||||
|
||||
private LiveQueryException() {
|
||||
super();
|
||||
}
|
||||
|
||||
private LiveQueryException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
private LiveQueryException(String detailMessage, Throwable cause) {
|
||||
super(detailMessage, cause);
|
||||
}
|
||||
|
||||
private LiveQueryException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* An error that is reported when any other unknown {@link RuntimeException} occurs unexpectedly.
|
||||
*/
|
||||
public static class UnknownException extends LiveQueryException {
|
||||
/* package */ UnknownException(String detailMessage, RuntimeException cause) {
|
||||
super(detailMessage, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An error that is reported when the server returns a response that cannot be parsed.
|
||||
*/
|
||||
public static class InvalidResponseException extends LiveQueryException {
|
||||
/* package */ InvalidResponseException(String response) {
|
||||
super(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An error that is reported when the server does not accept a query we've sent to it.
|
||||
*/
|
||||
public static class InvalidQueryException extends LiveQueryException {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An error that is reported when the server returns valid JSON, but it doesn't match the format we expect.
|
||||
*/
|
||||
public static class InvalidJSONException extends LiveQueryException {
|
||||
// JSON used for matching.
|
||||
private final String json;
|
||||
/// Key that was expected to match.
|
||||
private final String expectedKey;
|
||||
|
||||
/* package */ InvalidJSONException(String json, String expectedKey) {
|
||||
super(String.format(Locale.US, "Invalid JSON; expectedKey: %s, json: %s", expectedKey, json));
|
||||
this.json = json;
|
||||
this.expectedKey = expectedKey;
|
||||
}
|
||||
|
||||
public String getJson() {
|
||||
return json;
|
||||
}
|
||||
|
||||
public String getExpectedKey() {
|
||||
return expectedKey;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An error that is reported when the live query server encounters an internal error.
|
||||
*/
|
||||
public static class ServerReportedException extends LiveQueryException {
|
||||
|
||||
private final int code;
|
||||
private final String error;
|
||||
private final boolean reconnect;
|
||||
|
||||
public ServerReportedException(int code, String error, boolean reconnect) {
|
||||
super(String.format(Locale.US, "Server reported error; code: %d, error: %s, reconnect: %b", code, error, reconnect));
|
||||
this.code = code;
|
||||
this.error = error;
|
||||
this.reconnect = reconnect;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public boolean isReconnect() {
|
||||
return reconnect;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package com.parse;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Locale;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
import okio.ByteString;
|
||||
|
||||
/* package */ public class OkHttp3SocketClientFactory implements WebSocketClientFactory {
|
||||
|
||||
OkHttpClient mClient;
|
||||
|
||||
public OkHttp3SocketClientFactory(OkHttpClient client) {
|
||||
mClient = client;
|
||||
}
|
||||
|
||||
public OkHttp3SocketClientFactory() {
|
||||
mClient = new OkHttpClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketClient createInstance(WebSocketClient.WebSocketClientCallback webSocketClientCallback, URI hostUrl) {
|
||||
return new OkHttp3WebSocketClient(mClient, webSocketClientCallback, hostUrl);
|
||||
}
|
||||
|
||||
static class OkHttp3WebSocketClient implements WebSocketClient {
|
||||
|
||||
private static final String LOG_TAG = "OkHttpWebSocketClient";
|
||||
|
||||
private final WebSocketClientCallback webSocketClientCallback;
|
||||
private WebSocket webSocket;
|
||||
private State state = State.NONE;
|
||||
private final OkHttpClient client;
|
||||
private final String url;
|
||||
private final int STATUS_CODE = 1000;
|
||||
private final String CLOSING_MSG = "User invoked close";
|
||||
|
||||
private final WebSocketListener handler = new WebSocketListener() {
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
setState(State.CONNECTED);
|
||||
webSocketClientCallback.onOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, String text) {
|
||||
webSocketClientCallback.onMessage(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, ByteString bytes) {
|
||||
Log.w(LOG_TAG, String.format(Locale.US,
|
||||
"Socket got into inconsistent state and received %s instead.",
|
||||
bytes.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
setState(State.DISCONNECTED);
|
||||
webSocketClientCallback.onClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(okhttp3.WebSocket webSocket, Throwable t, Response response) {
|
||||
webSocketClientCallback.onError(t);
|
||||
}
|
||||
};
|
||||
|
||||
private OkHttp3WebSocketClient(OkHttpClient okHttpClient,
|
||||
WebSocketClientCallback webSocketClientCallback, URI hostUrl) {
|
||||
client = okHttpClient;
|
||||
this.webSocketClientCallback = webSocketClientCallback;
|
||||
url = hostUrl.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void open() {
|
||||
if (State.NONE == state) {
|
||||
// OkHttp3 connects as soon as the socket is created so do it here.
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.build();
|
||||
|
||||
webSocket = client.newWebSocket(request, handler);
|
||||
setState(State.CONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
setState(State.DISCONNECTING);
|
||||
webSocket.close(STATUS_CODE, CLOSING_MSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String message) {
|
||||
if (state == State.CONNECTED) {
|
||||
webSocket.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
private synchronized void setState(State newState) {
|
||||
this.state = newState;
|
||||
this.webSocketClientCallback.stateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.parse;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public interface ParseLiveQueryClient {
|
||||
<T extends ParseObject> SubscriptionHandling<T> subscribe(ParseQuery<T> query);
|
||||
|
||||
<T extends ParseObject> void unsubscribe(final ParseQuery<T> query);
|
||||
|
||||
<T extends ParseObject> void unsubscribe(final ParseQuery<T> query, final SubscriptionHandling<T> subscriptionHandling);
|
||||
|
||||
void connectIfNeeded();
|
||||
|
||||
void reconnect();
|
||||
|
||||
void disconnect();
|
||||
|
||||
void registerListener(ParseLiveQueryClientCallbacks listener);
|
||||
|
||||
void unregisterListener(ParseLiveQueryClientCallbacks listener);
|
||||
|
||||
class Factory {
|
||||
|
||||
public static ParseLiveQueryClient getClient() {
|
||||
return new ParseLiveQueryClientImpl();
|
||||
}
|
||||
|
||||
public static ParseLiveQueryClient getClient(WebSocketClientFactory webSocketClientFactory) {
|
||||
return new ParseLiveQueryClientImpl(webSocketClientFactory);
|
||||
}
|
||||
|
||||
public static ParseLiveQueryClient getClient(URI uri) {
|
||||
return new ParseLiveQueryClientImpl(uri);
|
||||
}
|
||||
|
||||
public static ParseLiveQueryClient getClient(URI uri, WebSocketClientFactory webSocketClientFactory) {
|
||||
return new ParseLiveQueryClientImpl(uri, webSocketClientFactory);
|
||||
}
|
||||
|
||||
/* package */
|
||||
static ParseLiveQueryClient getClient(URI uri, WebSocketClientFactory webSocketClientFactory, Executor taskExecutor) {
|
||||
return new ParseLiveQueryClientImpl(uri, webSocketClientFactory, taskExecutor);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.parse;
|
||||
|
||||
public interface ParseLiveQueryClientCallbacks {
|
||||
void onLiveQueryClientConnected(ParseLiveQueryClient client);
|
||||
|
||||
void onLiveQueryClientDisconnected(ParseLiveQueryClient client, boolean userInitiated);
|
||||
|
||||
void onLiveQueryError(ParseLiveQueryClient client, LiveQueryException reason);
|
||||
|
||||
void onSocketError(ParseLiveQueryClient client, Throwable reason);
|
||||
}
|
@ -0,0 +1,435 @@
|
||||
package com.parse;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import bolts.Continuation;
|
||||
import bolts.Task;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
import static com.parse.Parse.checkInit;
|
||||
|
||||
/* package */ class ParseLiveQueryClientImpl implements ParseLiveQueryClient {
|
||||
|
||||
private static final String LOG_TAG = "ParseLiveQueryClient";
|
||||
|
||||
private final Executor taskExecutor;
|
||||
private final String applicationId;
|
||||
private final String clientKey;
|
||||
private final SparseArray<Subscription<? extends ParseObject>> subscriptions = new SparseArray<>();
|
||||
private final URI uri;
|
||||
private final WebSocketClientFactory webSocketClientFactory;
|
||||
private final WebSocketClient.WebSocketClientCallback webSocketClientCallback;
|
||||
|
||||
private final List<ParseLiveQueryClientCallbacks> mCallbacks = new ArrayList<>();
|
||||
|
||||
private WebSocketClient webSocketClient;
|
||||
private int requestIdCount = 1;
|
||||
private boolean userInitiatedDisconnect = false;
|
||||
private boolean hasReceivedConnected = false;
|
||||
|
||||
/* package */ ParseLiveQueryClientImpl() {
|
||||
this(getDefaultUri());
|
||||
}
|
||||
|
||||
/* package */ ParseLiveQueryClientImpl(URI uri) {
|
||||
this(uri, new OkHttp3SocketClientFactory(new OkHttpClient()), Task.BACKGROUND_EXECUTOR);
|
||||
}
|
||||
|
||||
/* package */ ParseLiveQueryClientImpl(URI uri, WebSocketClientFactory webSocketClientFactory) {
|
||||
this(uri, webSocketClientFactory, Task.BACKGROUND_EXECUTOR);
|
||||
}
|
||||
|
||||
/* package */ ParseLiveQueryClientImpl(WebSocketClientFactory webSocketClientFactory) {
|
||||
this(getDefaultUri(), webSocketClientFactory, Task.BACKGROUND_EXECUTOR);
|
||||
}
|
||||
|
||||
/* package */ ParseLiveQueryClientImpl(URI uri, WebSocketClientFactory webSocketClientFactory, Executor taskExecutor) {
|
||||
checkInit();
|
||||
this.uri = uri;
|
||||
this.applicationId = ParsePlugins.get().applicationId();
|
||||
this.clientKey = ParsePlugins.get().clientKey();
|
||||
this.webSocketClientFactory = webSocketClientFactory;
|
||||
this.taskExecutor = taskExecutor;
|
||||
this.webSocketClientCallback = getWebSocketClientCallback();
|
||||
}
|
||||
|
||||
private static URI getDefaultUri() {
|
||||
URL serverUrl = ParseRESTCommand.server;
|
||||
if (serverUrl == null) return null;
|
||||
String url = serverUrl.toString();
|
||||
if (serverUrl.getProtocol().equals("https")) {
|
||||
url = url.replaceFirst("https", "wss");
|
||||
} else {
|
||||
url = url.replaceFirst("http", "ws");
|
||||
}
|
||||
try {
|
||||
return new URI(url);
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends ParseObject> SubscriptionHandling<T> subscribe(ParseQuery<T> query) {
|
||||
int requestId = requestIdGenerator();
|
||||
Subscription<T> subscription = new Subscription<>(requestId, query);
|
||||
subscriptions.append(requestId, subscription);
|
||||
|
||||
if (isConnected()) {
|
||||
sendSubscription(subscription);
|
||||
} else if (userInitiatedDisconnect) {
|
||||
Log.w(LOG_TAG, "Warning: The client was explicitly disconnected! You must explicitly call .reconnect() in order to process your subscriptions.");
|
||||
} else {
|
||||
connectIfNeeded();
|
||||
}
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
public void connectIfNeeded() {
|
||||
switch (getWebSocketState()) {
|
||||
case CONNECTED:
|
||||
// nothing to do
|
||||
break;
|
||||
case CONNECTING:
|
||||
// just wait for it to finish connecting
|
||||
break;
|
||||
|
||||
case NONE:
|
||||
case DISCONNECTING:
|
||||
case DISCONNECTED:
|
||||
reconnect();
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends ParseObject> void unsubscribe(final ParseQuery<T> query) {
|
||||
if (query != null) {
|
||||
for (int i = 0; i < subscriptions.size(); i++) {
|
||||
Subscription subscription = subscriptions.valueAt(i);
|
||||
if (query.equals(subscription.getQuery())) {
|
||||
sendUnsubscription(subscription);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends ParseObject> void unsubscribe(final ParseQuery<T> query, final SubscriptionHandling<T> subscriptionHandling) {
|
||||
if (query != null && subscriptionHandling != null) {
|
||||
for (int i = 0; i < subscriptions.size(); i++) {
|
||||
Subscription subscription = subscriptions.valueAt(i);
|
||||
if (query.equals(subscription.getQuery()) && subscriptionHandling.equals(subscription)) {
|
||||
sendUnsubscription(subscription);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconnect() {
|
||||
if (webSocketClient != null) {
|
||||
webSocketClient.close();
|
||||
}
|
||||
|
||||
userInitiatedDisconnect = false;
|
||||
hasReceivedConnected = false;
|
||||
webSocketClient = webSocketClientFactory.createInstance(webSocketClientCallback, uri);
|
||||
webSocketClient.open();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
if (webSocketClient != null) {
|
||||
webSocketClient.close();
|
||||
webSocketClient = null;
|
||||
}
|
||||
|
||||
userInitiatedDisconnect = true;
|
||||
hasReceivedConnected = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener(ParseLiveQueryClientCallbacks listener) {
|
||||
mCallbacks.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListener(ParseLiveQueryClientCallbacks listener) {
|
||||
mCallbacks.remove(listener);
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
private synchronized int requestIdGenerator() {
|
||||
return requestIdCount++;
|
||||
}
|
||||
|
||||
private WebSocketClient.State getWebSocketState() {
|
||||
WebSocketClient.State state = webSocketClient == null ? null : webSocketClient.getState();
|
||||
return state == null ? WebSocketClient.State.NONE : state;
|
||||
}
|
||||
|
||||
private boolean isConnected() {
|
||||
return hasReceivedConnected && inAnyState(WebSocketClient.State.CONNECTED);
|
||||
}
|
||||
|
||||
private boolean inAnyState(WebSocketClient.State... states) {
|
||||
return Arrays.asList(states).contains(getWebSocketState());
|
||||
}
|
||||
|
||||
private Task<Void> handleOperationAsync(final String message) {
|
||||
return Task.call(new Callable<Void>() {
|
||||
public Void call() throws Exception {
|
||||
parseMessage(message);
|
||||
return null;
|
||||
}
|
||||
}, taskExecutor);
|
||||
}
|
||||
|
||||
private Task<Void> sendOperationAsync(final ClientOperation clientOperation) {
|
||||
return Task.call(new Callable<Void>() {
|
||||
public Void call() throws Exception {
|
||||
JSONObject jsonEncoded = clientOperation.getJSONObjectRepresentation();
|
||||
String jsonString = jsonEncoded.toString();
|
||||
if (Parse.getLogLevel() <= Parse.LOG_LEVEL_DEBUG) {
|
||||
Log.d(LOG_TAG, "Sending over websocket: " + jsonString);
|
||||
}
|
||||
webSocketClient.send(jsonString);
|
||||
return null;
|
||||
}
|
||||
}, taskExecutor);
|
||||
}
|
||||
|
||||
private void parseMessage(String message) throws LiveQueryException {
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject(message);
|
||||
String rawOperation = jsonObject.getString("op");
|
||||
|
||||
switch (rawOperation) {
|
||||
case "connected":
|
||||
hasReceivedConnected = true;
|
||||
dispatchConnected();
|
||||
Log.v(LOG_TAG, "Connected, sending pending subscription");
|
||||
for (int i = 0; i < subscriptions.size(); i++) {
|
||||
sendSubscription(subscriptions.valueAt(i));
|
||||
}
|
||||
break;
|
||||
case "redirect":
|
||||
String url = jsonObject.getString("url");
|
||||
// TODO: Handle redirect.
|
||||
Log.d(LOG_TAG, "Redirect is not yet handled");
|
||||
break;
|
||||
case "subscribed":
|
||||
handleSubscribedEvent(jsonObject);
|
||||
break;
|
||||
case "unsubscribed":
|
||||
handleUnsubscribedEvent(jsonObject);
|
||||
break;
|
||||
case "enter":
|
||||
handleObjectEvent(Subscription.Event.ENTER, jsonObject);
|
||||
break;
|
||||
case "leave":
|
||||
handleObjectEvent(Subscription.Event.LEAVE, jsonObject);
|
||||
break;
|
||||
case "update":
|
||||
handleObjectEvent(Subscription.Event.UPDATE, jsonObject);
|
||||
break;
|
||||
case "create":
|
||||
handleObjectEvent(Subscription.Event.CREATE, jsonObject);
|
||||
break;
|
||||
case "delete":
|
||||
handleObjectEvent(Subscription.Event.DELETE, jsonObject);
|
||||
break;
|
||||
case "error":
|
||||
handleErrorEvent(jsonObject);
|
||||
break;
|
||||
default:
|
||||
throw new LiveQueryException.InvalidResponseException(message);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
throw new LiveQueryException.InvalidResponseException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchConnected() {
|
||||
for (ParseLiveQueryClientCallbacks callback : mCallbacks) {
|
||||
callback.onLiveQueryClientConnected(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchDisconnected() {
|
||||
for (ParseLiveQueryClientCallbacks callback : mCallbacks) {
|
||||
callback.onLiveQueryClientDisconnected(this, userInitiatedDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void dispatchServerError(LiveQueryException exc) {
|
||||
for (ParseLiveQueryClientCallbacks callback : mCallbacks) {
|
||||
callback.onLiveQueryError(this, exc);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchSocketError(Throwable reason) {
|
||||
userInitiatedDisconnect = false;
|
||||
|
||||
for (ParseLiveQueryClientCallbacks callback : mCallbacks) {
|
||||
callback.onSocketError(this, reason);
|
||||
}
|
||||
|
||||
dispatchDisconnected();
|
||||
}
|
||||
|
||||
private <T extends ParseObject> void handleSubscribedEvent(JSONObject jsonObject) throws JSONException {
|
||||
final int requestId = jsonObject.getInt("requestId");
|
||||
final Subscription<T> subscription = subscriptionForRequestId(requestId);
|
||||
if (subscription != null) {
|
||||
subscription.didSubscribe(subscription.getQuery());
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends ParseObject> void handleUnsubscribedEvent(JSONObject jsonObject) throws JSONException {
|
||||
final int requestId = jsonObject.getInt("requestId");
|
||||
final Subscription<T> subscription = subscriptionForRequestId(requestId);
|
||||
if (subscription != null) {
|
||||
subscription.didUnsubscribe(subscription.getQuery());
|
||||
subscriptions.remove(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends ParseObject> void handleObjectEvent(Subscription.Event event, JSONObject jsonObject) throws JSONException {
|
||||
final int requestId = jsonObject.getInt("requestId");
|
||||
final Subscription<T> subscription = subscriptionForRequestId(requestId);
|
||||
if (subscription != null) {
|
||||
T object = ParseObject.fromJSON(jsonObject.getJSONObject("object"), subscription.getQueryState().className(), ParseDecoder.get(), subscription.getQueryState().selectedKeys());
|
||||
subscription.didReceive(event, subscription.getQuery(), object);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends ParseObject> void handleErrorEvent(JSONObject jsonObject) throws JSONException {
|
||||
int requestId = jsonObject.getInt("requestId");
|
||||
int code = jsonObject.getInt("code");
|
||||
String error = jsonObject.getString("error");
|
||||
Boolean reconnect = jsonObject.getBoolean("reconnect");
|
||||
final Subscription<T> subscription = subscriptionForRequestId(requestId);
|
||||
LiveQueryException exc = new LiveQueryException.ServerReportedException(code, error, reconnect);
|
||||
|
||||
if (subscription != null) {
|
||||
subscription.didEncounter(exc, subscription.getQuery());
|
||||
}
|
||||
|
||||
dispatchServerError(exc);
|
||||
}
|
||||
|
||||
private <T extends ParseObject> Subscription<T> subscriptionForRequestId(int requestId) {
|
||||
//noinspection unchecked
|
||||
return (Subscription<T>) subscriptions.get(requestId);
|
||||
}
|
||||
|
||||
private <T extends ParseObject> void sendSubscription(final Subscription<T> subscription) {
|
||||
ParseUser.getCurrentSessionTokenAsync().onSuccess(new Continuation<String, Void>() {
|
||||
@Override
|
||||
public Void then(Task<String> task) throws Exception {
|
||||
String sessionToken = task.getResult();
|
||||
SubscribeClientOperation<T> op = new SubscribeClientOperation<>(subscription.getRequestId(), subscription.getQueryState(), sessionToken);
|
||||
|
||||
// dispatch errors
|
||||
sendOperationAsync(op).continueWith(new Continuation<Void, Void>() {
|
||||
public Void then(Task<Void> task) {
|
||||
Exception error = task.getError();
|
||||
if (error != null) {
|
||||
if (error instanceof RuntimeException) {
|
||||
subscription.didEncounter(new LiveQueryException.UnknownException(
|
||||
"Error when subscribing", (RuntimeException) error), subscription.getQuery());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void sendUnsubscription(Subscription subscription) {
|
||||
sendOperationAsync(new UnsubscribeClientOperation(subscription.getRequestId()));
|
||||
}
|
||||
|
||||
private WebSocketClient.WebSocketClientCallback getWebSocketClientCallback() {
|
||||
return new WebSocketClient.WebSocketClientCallback() {
|
||||
@Override
|
||||
public void onOpen() {
|
||||
hasReceivedConnected = false;
|
||||
Log.v(LOG_TAG, "Socket opened");
|
||||
ParseUser.getCurrentSessionTokenAsync().onSuccessTask(new Continuation<String, Task<Void>>() {
|
||||
@Override
|
||||
public Task<Void> then(Task<String> task) throws Exception {
|
||||
String sessionToken = task.getResult();
|
||||
return sendOperationAsync(new ConnectClientOperation(applicationId, sessionToken));
|
||||
}
|
||||
}).continueWith(new Continuation<Void, Void>() {
|
||||
public Void then(Task<Void> task) {
|
||||
Exception error = task.getError();
|
||||
if (error != null) {
|
||||
Log.e(LOG_TAG, "Error when connection client", error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
Log.v(LOG_TAG, "Socket onMessage " + message);
|
||||
handleOperationAsync(message).continueWith(new Continuation<Void, Void>() {
|
||||
public Void then(Task<Void> task) {
|
||||
Exception error = task.getError();
|
||||
if (error != null) {
|
||||
Log.e(LOG_TAG, "Error handling message", error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose() {
|
||||
Log.v(LOG_TAG, "Socket onClose");
|
||||
hasReceivedConnected = false;
|
||||
dispatchDisconnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable exception) {
|
||||
Log.e(LOG_TAG, "Socket onError", exception);
|
||||
hasReceivedConnected = false;
|
||||
dispatchSocketError(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged() {
|
||||
Log.v(LOG_TAG, "Socket stateChanged");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.parse;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/* package */ class SubscribeClientOperation<T extends ParseObject> extends ClientOperation {
|
||||
|
||||
private final int requestId;
|
||||
private final ParseQuery.State<T> state;
|
||||
private final String sessionToken;
|
||||
|
||||
/* package */ SubscribeClientOperation(int requestId, ParseQuery.State<T> state, String sessionToken) {
|
||||
this.requestId = requestId;
|
||||
this.state = state;
|
||||
this.sessionToken = sessionToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ JSONObject getJSONObjectRepresentation() throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("op", "subscribe");
|
||||
jsonObject.put("requestId", requestId);
|
||||
jsonObject.put("sessionToken", sessionToken);
|
||||
|
||||
JSONObject queryJsonObject = new JSONObject();
|
||||
queryJsonObject.put("className", state.className());
|
||||
|
||||
// TODO: add support for fields
|
||||
// https://github.com/ParsePlatform/parse-server/issues/3671
|
||||
|
||||
PointerEncoder pointerEncoder = PointerEncoder.get();
|
||||
queryJsonObject.put("where", pointerEncoder.encode(state.constraints()));
|
||||
|
||||
jsonObject.put("query", queryJsonObject);
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
package com.parse;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/* package */ class Subscription<T extends ParseObject> implements SubscriptionHandling<T> {
|
||||
|
||||
private final List<HandleEventsCallback<T>> handleEventsCallbacks = new ArrayList<>();
|
||||
private final List<HandleErrorCallback<T>> handleErrorCallbacks = new ArrayList<>();
|
||||
private final List<HandleSubscribeCallback<T>> handleSubscribeCallbacks = new ArrayList<>();
|
||||
private final List<HandleUnsubscribeCallback<T>> handleUnsubscribeCallbacks = new ArrayList<>();
|
||||
|
||||
private final int requestId;
|
||||
private final ParseQuery<T> query;
|
||||
private final ParseQuery.State<T> state;
|
||||
|
||||
/* package */ Subscription(int requestId, ParseQuery<T> query) {
|
||||
this.requestId = requestId;
|
||||
this.query = query;
|
||||
this.state = query.getBuilder().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscription<T> handleEvents(HandleEventsCallback<T> callback) {
|
||||
handleEventsCallbacks.add(callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscription<T> handleEvent(final Event event, final HandleEventCallback<T> callback) {
|
||||
return handleEvents(new HandleEventsCallback<T>() {
|
||||
@Override
|
||||
public void onEvents(ParseQuery<T> query, Event callbackEvent, T object) {
|
||||
if (callbackEvent == event) {
|
||||
callback.onEvent(query, object);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscription<T> handleError(HandleErrorCallback<T> callback) {
|
||||
handleErrorCallbacks.add(callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscription<T> handleSubscribe(HandleSubscribeCallback<T> callback) {
|
||||
handleSubscribeCallbacks.add(callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscription<T> handleUnsubscribe(HandleUnsubscribeCallback<T> callback) {
|
||||
handleUnsubscribeCallbacks.add(callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
/* package */ ParseQuery<T> getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
/* package */ ParseQuery.State<T> getQueryState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the handler that an event has been received from the live query server.
|
||||
*
|
||||
* @param event The event that has been received from the server.
|
||||
* @param query The query that the event occurred on.
|
||||
*/
|
||||
/* package */ void didReceive(Event event, ParseQuery<T> query, T object) {
|
||||
for (HandleEventsCallback<T> handleEventsCallback : handleEventsCallbacks) {
|
||||
handleEventsCallback.onEvents(query, event, object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the handler that an error has been received from the live query server.
|
||||
*
|
||||
* @param error The error that the server has encountered.
|
||||
* @param query The query that the error occurred on.
|
||||
*/
|
||||
/* package */ void didEncounter(LiveQueryException error, ParseQuery<T> query) {
|
||||
for (HandleErrorCallback<T> handleErrorCallback : handleErrorCallbacks) {
|
||||
handleErrorCallback.onError(query, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the handler that a query has been successfully registered with the server.
|
||||
* - note: This may be invoked multiple times if the client disconnects/reconnects.
|
||||
*
|
||||
* @param query The query that has been subscribed.
|
||||
*/
|
||||
/* package */ void didSubscribe(ParseQuery<T> query) {
|
||||
for (HandleSubscribeCallback<T> handleSubscribeCallback : handleSubscribeCallbacks) {
|
||||
handleSubscribeCallback.onSubscribe(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the handler that a query has been successfully deregistered from the server.
|
||||
* - note: This is not called unless `unregister()` is explicitly called.
|
||||
*
|
||||
* @param query The query that has been unsubscribed.
|
||||
*/
|
||||
/* package */ void didUnsubscribe(ParseQuery<T> query) {
|
||||
for (HandleUnsubscribeCallback<T> handleUnsubscribeCallback : handleUnsubscribeCallbacks) {
|
||||
handleUnsubscribeCallback.onUnsubscribe(query);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.parse;
|
||||
|
||||
public interface SubscriptionHandling<T extends ParseObject> {
|
||||
|
||||
/**
|
||||
* Register a callback for when an event occurs.
|
||||
*
|
||||
* @param callback The callback to register.
|
||||
* @return The same SubscriptionHandling, for easy chaining.
|
||||
*/
|
||||
SubscriptionHandling<T> handleEvents(Subscription.HandleEventsCallback<T> callback);
|
||||
|
||||
/**
|
||||
* Register a callback for when an event occurs.
|
||||
*
|
||||
* @param event The event type to handle. You should pass one of the enum cases in Event
|
||||
* @param callback The callback to register.
|
||||
* @return The same SubscriptionHandling, for easy chaining.
|
||||
*/
|
||||
SubscriptionHandling<T> handleEvent(Subscription.Event event, Subscription.HandleEventCallback<T> callback);
|
||||
|
||||
/**
|
||||
* Register a callback for when an event occurs.
|
||||
*
|
||||
* @param callback The callback to register.
|
||||
* @return The same SubscriptionHandling, for easy chaining.
|
||||
*/
|
||||
SubscriptionHandling<T> handleError(Subscription.HandleErrorCallback<T> callback);
|
||||
|
||||
/**
|
||||
* Register a callback for when a client succesfully subscribes to a query.
|
||||
*
|
||||
* @param callback The callback to register.
|
||||
* @return The same SubscriptionHandling, for easy chaining.
|
||||
*/
|
||||
SubscriptionHandling<T> handleSubscribe(Subscription.HandleSubscribeCallback<T> callback);
|
||||
|
||||
/**
|
||||
* Register a callback for when a query has been unsubscribed.
|
||||
*
|
||||
* @param callback The callback to register.
|
||||
* @return The same SubscriptionHandling, for easy chaining.
|
||||
*/
|
||||
SubscriptionHandling<T> handleUnsubscribe(Subscription.HandleUnsubscribeCallback<T> callback);
|
||||
|
||||
int getRequestId();
|
||||
|
||||
interface HandleEventsCallback<T extends ParseObject> {
|
||||
void onEvents(ParseQuery<T> query, Subscription.Event event, T object);
|
||||
}
|
||||
|
||||
interface HandleEventCallback<T extends ParseObject> {
|
||||
void onEvent(ParseQuery<T> query, T object);
|
||||
}
|
||||
|
||||
interface HandleErrorCallback<T extends ParseObject> {
|
||||
void onError(ParseQuery<T> query, LiveQueryException exception);
|
||||
}
|
||||
|
||||
interface HandleSubscribeCallback<T extends ParseObject> {
|
||||
void onSubscribe(ParseQuery<T> query);
|
||||
}
|
||||
|
||||
interface HandleUnsubscribeCallback<T extends ParseObject> {
|
||||
void onUnsubscribe(ParseQuery<T> query);
|
||||
}
|
||||
|
||||
enum Event {
|
||||
CREATE, ENTER, UPDATE, LEAVE, DELETE
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.parse;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/* package */ class UnsubscribeClientOperation extends ClientOperation {
|
||||
|
||||
private final int requestId;
|
||||
|
||||
/* package */ UnsubscribeClientOperation(int requestId) {
|
||||
this.requestId = requestId;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ JSONObject getJSONObjectRepresentation() throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("op", "unsubscribe");
|
||||
jsonObject.put("requestId", requestId);
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.parse;
|
||||
|
||||
/* package */ interface WebSocketClient {
|
||||
|
||||
void open();
|
||||
|
||||
void close();
|
||||
|
||||
void send(String message);
|
||||
|
||||
State getState();
|
||||
|
||||
interface WebSocketClientCallback {
|
||||
void onOpen();
|
||||
|
||||
void onMessage(String message);
|
||||
|
||||
void onClose();
|
||||
|
||||
void onError(Throwable exception);
|
||||
|
||||
void stateChanged();
|
||||
}
|
||||
|
||||
enum State {NONE, CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.parse;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/* package */ interface WebSocketClientFactory {
|
||||
|
||||
WebSocketClient createInstance(WebSocketClient.WebSocketClientCallback webSocketClientCallback, URI hostUrl);
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.parse;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
class ImmediateExecutor implements Executor {
|
||||
@Override
|
||||
public void execute(Runnable runnable) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
@ -0,0 +1,609 @@
|
||||
package com.parse;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.util.Transcript;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import bolts.Task;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.mockito.AdditionalMatchers.and;
|
||||
import static org.mockito.AdditionalMatchers.not;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyBoolean;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.contains;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import com.parse.livequery.BuildConfig;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, sdk = 21)
|
||||
public class TestParseLiveQueryClient {
|
||||
|
||||
private WebSocketClient webSocketClient;
|
||||
private WebSocketClient.WebSocketClientCallback webSocketClientCallback;
|
||||
private ParseLiveQueryClient parseLiveQueryClient;
|
||||
|
||||
private ParseUser mockUser;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ParsePlugins.initialize("1234", "1234");
|
||||
|
||||
// Register a mock currentUserController to make getCurrentUser work
|
||||
mockUser = mock(ParseUser.class);
|
||||
ParseCurrentUserController currentUserController = mock(ParseCurrentUserController.class);
|
||||
when(currentUserController.getAsync(anyBoolean())).thenAnswer(new Answer<Task<ParseUser>>() {
|
||||
@Override
|
||||
public Task<ParseUser> answer(InvocationOnMock invocation) throws Throwable {
|
||||
return Task.forResult(mockUser);
|
||||
}
|
||||
});
|
||||
when(currentUserController.getCurrentSessionTokenAsync()).thenAnswer(new Answer<Task<String>>() {
|
||||
@Override
|
||||
public Task<String> answer(InvocationOnMock invocation) throws Throwable {
|
||||
return Task.forResult(mockUser.getSessionToken());
|
||||
}
|
||||
});
|
||||
ParseCorePlugins.getInstance().registerCurrentUserController(currentUserController);
|
||||
|
||||
parseLiveQueryClient = ParseLiveQueryClient.Factory.getClient(new URI(""), new WebSocketClientFactory() {
|
||||
@Override
|
||||
public WebSocketClient createInstance(WebSocketClient.WebSocketClientCallback webSocketClientCallback, URI hostUrl) {
|
||||
TestParseLiveQueryClient.this.webSocketClientCallback = webSocketClientCallback;
|
||||
webSocketClient = mock(WebSocketClient.class);
|
||||
return webSocketClient;
|
||||
}
|
||||
}, new ImmediateExecutor());
|
||||
reconnect();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
ParseCorePlugins.getInstance().reset();
|
||||
ParsePlugins.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscribeAfterSocketConnectBeforeConnectedOp() throws Exception {
|
||||
// Bug: https://github.com/parse-community/ParseLiveQuery-Android/issues/46
|
||||
ParseQuery<ParseObject> queryA = ParseQuery.getQuery("objA");
|
||||
ParseQuery<ParseObject> queryB = ParseQuery.getQuery("objB");
|
||||
clearConnection();
|
||||
|
||||
// This will trigger connectIfNeeded(), which calls reconnect()
|
||||
SubscriptionHandling<ParseObject> subA = parseLiveQueryClient.subscribe(queryA);
|
||||
|
||||
verify(webSocketClient, times(1)).open();
|
||||
verify(webSocketClient, never()).send(anyString());
|
||||
|
||||
// Now the socket is open
|
||||
webSocketClientCallback.onOpen();
|
||||
when(webSocketClient.getState()).thenReturn(WebSocketClient.State.CONNECTED);
|
||||
// and we send op=connect
|
||||
verify(webSocketClient, times(1)).send(contains("\"op\":\"connect\""));
|
||||
|
||||
// Now if we subscribe to queryB, we SHOULD NOT send the subscribe yet, until we get op=connected
|
||||
SubscriptionHandling<ParseObject> subB = parseLiveQueryClient.subscribe(queryB);
|
||||
verify(webSocketClient, never()).send(contains("\"op\":\"subscribe\""));
|
||||
|
||||
// on op=connected, _then_ we should send both subscriptions
|
||||
webSocketClientCallback.onMessage(createConnectedMessage().toString());
|
||||
verify(webSocketClient, times(2)).send(contains("\"op\":\"subscribe\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscribeWhenSubscribedToCallback() throws Exception {
|
||||
SubscriptionHandling.HandleSubscribeCallback<ParseObject> subscribeMockCallback = mock(SubscriptionHandling.HandleSubscribeCallback.class);
|
||||
|
||||
ParseQuery<ParseObject> parseQuery = new ParseQuery<>("test");
|
||||
createSubscription(parseQuery, subscribeMockCallback);
|
||||
|
||||
verify(subscribeMockCallback, times(1)).onSubscribe(parseQuery);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsubscribeWhenSubscribedToCallback() throws Exception {
|
||||
ParseQuery<ParseObject> parseQuery = new ParseQuery<>("test");
|
||||
SubscriptionHandling<ParseObject> subscriptionHandling = createSubscription(parseQuery,
|
||||
mock(SubscriptionHandling.HandleSubscribeCallback.class));
|
||||
|
||||
parseLiveQueryClient.unsubscribe(parseQuery);
|
||||
verify(webSocketClient, times(1)).send(any(String.class));
|
||||
|
||||
SubscriptionHandling.HandleUnsubscribeCallback<ParseObject> unsubscribeMockCallback = mock(
|
||||
SubscriptionHandling.HandleUnsubscribeCallback.class);
|
||||
subscriptionHandling.handleUnsubscribe(unsubscribeMockCallback);
|
||||
webSocketClientCallback.onMessage(createUnsubscribedMessage(subscriptionHandling.getRequestId()).toString());
|
||||
|
||||
verify(unsubscribeMockCallback, times(1)).onUnsubscribe(parseQuery);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testErrorWhileSubscribing() throws Exception {
|
||||
ParseQuery.State state = mock(ParseQuery.State.class);
|
||||
when(state.constraints()).thenThrow(new RuntimeException("forced error"));
|
||||
|
||||
ParseQuery.State.Builder builder = mock(ParseQuery.State.Builder.class);
|
||||
when(builder.build()).thenReturn(state);
|
||||
ParseQuery query = mock(ParseQuery.class);
|
||||
when(query.getBuilder()).thenReturn(builder);
|
||||
|
||||
SubscriptionHandling handling = parseLiveQueryClient.subscribe(query);
|
||||
|
||||
SubscriptionHandling.HandleErrorCallback<ParseObject> errorMockCallback = mock(SubscriptionHandling.HandleErrorCallback.class);
|
||||
handling.handleError(errorMockCallback);
|
||||
|
||||
// Trigger a re-subscribe
|
||||
webSocketClientCallback.onMessage(createConnectedMessage().toString());
|
||||
|
||||
// This will never get a chance to call op=subscribe, because an exception was thrown
|
||||
verify(webSocketClient, never()).send(anyString());
|
||||
|
||||
ArgumentCaptor<LiveQueryException> errorCaptor = ArgumentCaptor.forClass(LiveQueryException.class);
|
||||
verify(errorMockCallback, times(1)).onError(eq(query), errorCaptor.capture());
|
||||
|
||||
assertEquals("Error when subscribing", errorCaptor.getValue().getMessage());
|
||||
assertNotNull(errorCaptor.getValue().getCause());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testErrorWhenSubscribedToCallback() throws Exception {
|
||||
ParseQuery<ParseObject> parseQuery = new ParseQuery<>("test");
|
||||
SubscriptionHandling<ParseObject> subscriptionHandling = createSubscription(parseQuery,
|
||||
mock(SubscriptionHandling.HandleSubscribeCallback.class));
|
||||
|
||||
SubscriptionHandling.HandleErrorCallback<ParseObject> errorMockCallback = mock(SubscriptionHandling.HandleErrorCallback.class);
|
||||
subscriptionHandling.handleError(errorMockCallback);
|
||||
webSocketClientCallback.onMessage(createErrorMessage(subscriptionHandling.getRequestId()).toString());
|
||||
|
||||
ArgumentCaptor<LiveQueryException> errorCaptor = ArgumentCaptor.forClass(LiveQueryException.class);
|
||||
verify(errorMockCallback, times(1)).onError(eq(parseQuery), errorCaptor.capture());
|
||||
|
||||
LiveQueryException genericError = errorCaptor.getValue();
|
||||
assertTrue(genericError instanceof LiveQueryException.ServerReportedException);
|
||||
|
||||
LiveQueryException.ServerReportedException serverError = (LiveQueryException.ServerReportedException) genericError;
|
||||
assertEquals(serverError.getError(), "testError");
|
||||
assertEquals(serverError.getCode(), 1);
|
||||
assertEquals(serverError.isReconnect(), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeterogeneousSubscriptions() throws Exception {
|
||||
ParseObject.registerSubclass(MockClassA.class);
|
||||
ParseObject.registerSubclass(MockClassB.class);
|
||||
|
||||
ParseQuery<MockClassA> query1 = ParseQuery.getQuery(MockClassA.class);
|
||||
ParseQuery<MockClassB> query2 = ParseQuery.getQuery(MockClassB.class);
|
||||
SubscriptionHandling<MockClassA> handle1 = parseLiveQueryClient.subscribe(query1);
|
||||
SubscriptionHandling<MockClassB> handle2 = parseLiveQueryClient.subscribe(query2);
|
||||
|
||||
handle1.handleError(new SubscriptionHandling.HandleErrorCallback<MockClassA>() {
|
||||
@Override
|
||||
public void onError(ParseQuery<MockClassA> query, LiveQueryException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
});
|
||||
handle2.handleError(new SubscriptionHandling.HandleErrorCallback<MockClassB>() {
|
||||
@Override
|
||||
public void onError(ParseQuery<MockClassB> query, LiveQueryException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
});
|
||||
|
||||
SubscriptionHandling.HandleEventCallback<MockClassA> eventMockCallback1 = mock(SubscriptionHandling.HandleEventCallback.class);
|
||||
SubscriptionHandling.HandleEventCallback<MockClassB> eventMockCallback2 = mock(SubscriptionHandling.HandleEventCallback.class);
|
||||
|
||||
handle1.handleEvent(SubscriptionHandling.Event.CREATE, eventMockCallback1);
|
||||
handle2.handleEvent(SubscriptionHandling.Event.CREATE, eventMockCallback2);
|
||||
|
||||
ParseObject parseObject1 = new MockClassA();
|
||||
parseObject1.setObjectId("testId1");
|
||||
|
||||
ParseObject parseObject2 = new MockClassB();
|
||||
parseObject2.setObjectId("testId2");
|
||||
|
||||
webSocketClientCallback.onMessage(createObjectCreateMessage(handle1.getRequestId(), parseObject1).toString());
|
||||
webSocketClientCallback.onMessage(createObjectCreateMessage(handle2.getRequestId(), parseObject2).toString());
|
||||
|
||||
validateSameObject((SubscriptionHandling.HandleEventCallback) eventMockCallback1, (ParseQuery) query1, parseObject1);
|
||||
validateSameObject((SubscriptionHandling.HandleEventCallback) eventMockCallback2, (ParseQuery) query2, parseObject2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateEventWhenSubscribedToCallback() throws Exception {
|
||||
ParseQuery<ParseObject> parseQuery = new ParseQuery<>("test");
|
||||
SubscriptionHandling<ParseObject> subscriptionHandling = createSubscription(parseQuery,
|
||||
mock(SubscriptionHandling.HandleSubscribeCallback.class));
|
||||
|
||||
SubscriptionHandling.HandleEventCallback<ParseObject> eventMockCallback = mock(SubscriptionHandling.HandleEventCallback.class);
|
||||
subscriptionHandling.handleEvent(SubscriptionHandling.Event.CREATE, eventMockCallback);
|
||||
|
||||
ParseObject parseObject = new ParseObject("Test");
|
||||
parseObject.setObjectId("testId");
|
||||
|
||||
webSocketClientCallback.onMessage(createObjectCreateMessage(subscriptionHandling.getRequestId(), parseObject).toString());
|
||||
|
||||
validateSameObject(eventMockCallback, parseQuery, parseObject);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnterEventWhenSubscribedToCallback() throws Exception {
|
||||
ParseQuery<ParseObject> parseQuery = new ParseQuery<>("test");
|
||||
SubscriptionHandling<ParseObject> subscriptionHandling = createSubscription(parseQuery,
|
||||
mock(SubscriptionHandling.HandleSubscribeCallback.class));
|
||||
|
||||
SubscriptionHandling.HandleEventCallback<ParseObject> eventMockCallback = mock(SubscriptionHandling.HandleEventCallback.class);
|
||||
subscriptionHandling.handleEvent(SubscriptionHandling.Event.ENTER, eventMockCallback);
|
||||
|
||||
ParseObject parseObject = new ParseObject("Test");
|
||||
parseObject.setObjectId("testId");
|
||||
|
||||
webSocketClientCallback.onMessage(createObjectEnterMessage(subscriptionHandling.getRequestId(), parseObject).toString());
|
||||
|
||||
validateSameObject(eventMockCallback, parseQuery, parseObject);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateEventWhenSubscribedToCallback() throws Exception {
|
||||
ParseQuery<ParseObject> parseQuery = new ParseQuery<>("test");
|
||||
SubscriptionHandling<ParseObject> subscriptionHandling = createSubscription(parseQuery,
|
||||
mock(SubscriptionHandling.HandleSubscribeCallback.class));
|
||||
|
||||
SubscriptionHandling.HandleEventCallback<ParseObject> eventMockCallback = mock(SubscriptionHandling.HandleEventCallback.class);
|
||||
subscriptionHandling.handleEvent(SubscriptionHandling.Event.UPDATE, eventMockCallback);
|
||||
|
||||
ParseObject parseObject = new ParseObject("Test");
|
||||
parseObject.setObjectId("testId");
|
||||
|
||||
webSocketClientCallback.onMessage(createObjectUpdateMessage(subscriptionHandling.getRequestId(), parseObject).toString());
|
||||
|
||||
validateSameObject(eventMockCallback, parseQuery, parseObject);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeaveEventWhenSubscribedToCallback() throws Exception {
|
||||
ParseQuery<ParseObject> parseQuery = new ParseQuery<>("test");
|
||||
SubscriptionHandling<ParseObject> subscriptionHandling = createSubscription(parseQuery,
|
||||
mock(SubscriptionHandling.HandleSubscribeCallback.class));
|
||||
|
||||
SubscriptionHandling.HandleEventCallback<ParseObject> eventMockCallback = mock(SubscriptionHandling.HandleEventCallback.class);
|
||||
subscriptionHandling.handleEvent(SubscriptionHandling.Event.LEAVE, eventMockCallback);
|
||||
|
||||
ParseObject parseObject = new ParseObject("Test");
|
||||
parseObject.setObjectId("testId");
|
||||
|
||||
webSocketClientCallback.onMessage(createObjectLeaveMessage(subscriptionHandling.getRequestId(), parseObject).toString());
|
||||
|
||||
validateSameObject(eventMockCallback, parseQuery, parseObject);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteEventWhenSubscribedToCallback() throws Exception {
|
||||
ParseQuery<ParseObject> parseQuery = new ParseQuery<>("test");
|
||||
SubscriptionHandling<ParseObject> subscriptionHandling = createSubscription(parseQuery,
|
||||
mock(SubscriptionHandling.HandleSubscribeCallback.class));
|
||||
|
||||
SubscriptionHandling.HandleEventCallback<ParseObject> eventMockCallback = mock(SubscriptionHandling.HandleEventCallback.class);
|
||||
subscriptionHandling.handleEvent(SubscriptionHandling.Event.DELETE, eventMockCallback);
|
||||
|
||||
ParseObject parseObject = new ParseObject("Test");
|
||||
parseObject.setObjectId("testId");
|
||||
|
||||
webSocketClientCallback.onMessage(createObjectDeleteMessage(subscriptionHandling.getRequestId(), parseObject).toString());
|
||||
|
||||
validateSameObject(eventMockCallback, parseQuery, parseObject);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateEventWhenSubscribedToAnyCallback() throws Exception {
|
||||
ParseQuery<ParseObject> parseQuery = new ParseQuery<>("test");
|
||||
SubscriptionHandling<ParseObject> subscriptionHandling = createSubscription(parseQuery,
|
||||
mock(SubscriptionHandling.HandleSubscribeCallback.class));
|
||||
|
||||
SubscriptionHandling.HandleEventsCallback<ParseObject> eventsMockCallback = mock(SubscriptionHandling.HandleEventsCallback.class);
|
||||
subscriptionHandling.handleEvents(eventsMockCallback);
|
||||
|
||||
ParseObject parseObject = new ParseObject("Test");
|
||||
parseObject.setObjectId("testId");
|
||||
|
||||
webSocketClientCallback.onMessage(createObjectCreateMessage(subscriptionHandling.getRequestId(), parseObject).toString());
|
||||
|
||||
ArgumentCaptor<ParseObject> objectCaptor = ArgumentCaptor.forClass(ParseObject.class);
|
||||
verify(eventsMockCallback, times(1)).onEvents(eq(parseQuery), eq(SubscriptionHandling.Event.CREATE), objectCaptor.capture());
|
||||
|
||||
ParseObject newParseObject = objectCaptor.getValue();
|
||||
|
||||
assertEquals(parseObject.getObjectId(), newParseObject.getObjectId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionStoppedAfterUnsubscribe() throws Exception {
|
||||
ParseQuery<ParseObject> parseQuery = new ParseQuery<>("test");
|
||||
SubscriptionHandling<ParseObject> subscriptionHandling = createSubscription(parseQuery,
|
||||
mock(SubscriptionHandling.HandleSubscribeCallback.class));
|
||||
|
||||
SubscriptionHandling.HandleEventCallback<ParseObject> eventMockCallback = mock(SubscriptionHandling.HandleEventCallback.class);
|
||||
subscriptionHandling.handleEvent(SubscriptionHandling.Event.CREATE, eventMockCallback);
|
||||
|
||||
SubscriptionHandling.HandleUnsubscribeCallback<ParseObject> unsubscribeMockCallback = mock(
|
||||
SubscriptionHandling.HandleUnsubscribeCallback.class);
|
||||
subscriptionHandling.handleUnsubscribe(unsubscribeMockCallback);
|
||||
|
||||
parseLiveQueryClient.unsubscribe(parseQuery);
|
||||
verify(webSocketClient, times(1)).send(any(String.class));
|
||||
webSocketClientCallback.onMessage(createUnsubscribedMessage(subscriptionHandling.getRequestId()).toString());
|
||||
verify(unsubscribeMockCallback, times(1)).onUnsubscribe(parseQuery);
|
||||
|
||||
ParseObject parseObject = new ParseObject("Test");
|
||||
parseObject.setObjectId("testId");
|
||||
webSocketClientCallback.onMessage(createObjectCreateMessage(subscriptionHandling.getRequestId(), parseObject).toString());
|
||||
|
||||
ArgumentCaptor<ParseObject> objectCaptor = ArgumentCaptor.forClass(ParseObject.class);
|
||||
verify(eventMockCallback, times(0)).onEvent(eq(parseQuery), objectCaptor.capture());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionReplayedAfterReconnect() throws Exception {
|
||||
SubscriptionHandling.HandleSubscribeCallback<ParseObject> subscribeMockCallback = mock(SubscriptionHandling.HandleSubscribeCallback.class);
|
||||
|
||||
ParseQuery<ParseObject> parseQuery = new ParseQuery<>("test");
|
||||
createSubscription(parseQuery, subscribeMockCallback);
|
||||
|
||||
parseLiveQueryClient.disconnect();
|
||||
reconnect();
|
||||
|
||||
verify(webSocketClient, times(2)).send(any(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionTokenSentOnConnect() {
|
||||
when(mockUser.getSessionToken()).thenReturn("the token");
|
||||
parseLiveQueryClient.reconnect();
|
||||
webSocketClientCallback.onOpen();
|
||||
verify(webSocketClient, times(1)).send(contains("\"sessionToken\":\"the token\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptySessionTokenOnConnect() {
|
||||
parseLiveQueryClient.reconnect();
|
||||
webSocketClientCallback.onOpen();
|
||||
verify(webSocketClient, times(1)).send(not(contains("\"sessionToken\":")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionTokenSentOnSubscribe() {
|
||||
when(mockUser.getSessionToken()).thenReturn("the token");
|
||||
when(webSocketClient.getState()).thenReturn(WebSocketClient.State.CONNECTED);
|
||||
parseLiveQueryClient.subscribe(ParseQuery.getQuery("Test"));
|
||||
verify(webSocketClient, times(1)).send(and(
|
||||
contains("\"op\":\"subscribe\""),
|
||||
contains("\"sessionToken\":\"the token\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptySessionTokenOnSubscribe() {
|
||||
when(mockUser.getSessionToken()).thenReturn("the token");
|
||||
when(webSocketClient.getState()).thenReturn(WebSocketClient.State.CONNECTED);
|
||||
parseLiveQueryClient.subscribe(ParseQuery.getQuery("Test"));
|
||||
verify(webSocketClient, times(1)).send(contains("\"op\":\"connect\""));
|
||||
verify(webSocketClient, times(1)).send(and(
|
||||
contains("\"op\":\"subscribe\""),
|
||||
contains("\"sessionToken\":\"the token\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbackNotifiedOnUnexpectedDisconnect() throws Exception {
|
||||
LoggingCallbacks callbacks = new LoggingCallbacks();
|
||||
parseLiveQueryClient.registerListener(callbacks);
|
||||
callbacks.transcript.assertNoEventsSoFar();
|
||||
|
||||
// Unexpected close from the server:
|
||||
webSocketClientCallback.onClose();
|
||||
callbacks.transcript.assertEventsSoFar("onLiveQueryClientDisconnected: false");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbackNotifiedOnExpectedDisconnect() throws Exception {
|
||||
LoggingCallbacks callbacks = new LoggingCallbacks();
|
||||
parseLiveQueryClient.registerListener(callbacks);
|
||||
callbacks.transcript.assertNoEventsSoFar();
|
||||
|
||||
parseLiveQueryClient.disconnect();
|
||||
verify(webSocketClient, times(1)).close();
|
||||
|
||||
callbacks.transcript.assertNoEventsSoFar();
|
||||
// the client is a mock, so it won't actually invoke the callback automatically
|
||||
webSocketClientCallback.onClose();
|
||||
callbacks.transcript.assertEventsSoFar("onLiveQueryClientDisconnected: true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbackNotifiedOnConnect() throws Exception {
|
||||
LoggingCallbacks callbacks = new LoggingCallbacks();
|
||||
parseLiveQueryClient.registerListener(callbacks);
|
||||
callbacks.transcript.assertNoEventsSoFar();
|
||||
|
||||
reconnect();
|
||||
callbacks.transcript.assertEventsSoFar("onLiveQueryClientConnected");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbackNotifiedOnSocketError() throws Exception {
|
||||
LoggingCallbacks callbacks = new LoggingCallbacks();
|
||||
parseLiveQueryClient.registerListener(callbacks);
|
||||
callbacks.transcript.assertNoEventsSoFar();
|
||||
|
||||
webSocketClientCallback.onError(new IOException("bad things happened"));
|
||||
callbacks.transcript.assertEventsSoFar("onSocketError: java.io.IOException: bad things happened",
|
||||
"onLiveQueryClientDisconnected: false");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbackNotifiedOnServerError() throws Exception {
|
||||
LoggingCallbacks callbacks = new LoggingCallbacks();
|
||||
parseLiveQueryClient.registerListener(callbacks);
|
||||
callbacks.transcript.assertNoEventsSoFar();
|
||||
|
||||
webSocketClientCallback.onMessage(createErrorMessage(1).toString());
|
||||
callbacks.transcript.assertEventsSoFar("onLiveQueryError: com.parse.LiveQueryException$ServerReportedException: Server reported error; code: 1, error: testError, reconnect: true");
|
||||
}
|
||||
|
||||
private SubscriptionHandling<ParseObject> createSubscription(ParseQuery<ParseObject> parseQuery,
|
||||
SubscriptionHandling.HandleSubscribeCallback<ParseObject> subscribeMockCallback) throws Exception {
|
||||
SubscriptionHandling<ParseObject> subscriptionHandling = parseLiveQueryClient.subscribe(parseQuery).handleSubscribe(subscribeMockCallback);
|
||||
webSocketClientCallback.onMessage(createSubscribedMessage(subscriptionHandling.getRequestId()).toString());
|
||||
return subscriptionHandling;
|
||||
}
|
||||
|
||||
private void validateSameObject(SubscriptionHandling.HandleEventCallback<ParseObject> eventMockCallback,
|
||||
ParseQuery<ParseObject> parseQuery,
|
||||
ParseObject originalParseObject) {
|
||||
ArgumentCaptor<ParseObject> objectCaptor = ArgumentCaptor.forClass(ParseObject.class);
|
||||
verify(eventMockCallback, times(1)).onEvent(eq(parseQuery), objectCaptor.capture());
|
||||
|
||||
ParseObject newParseObject = objectCaptor.getValue();
|
||||
|
||||
assertEquals(originalParseObject.getClassName(), newParseObject.getClassName());
|
||||
assertEquals(originalParseObject.getObjectId(), newParseObject.getObjectId());
|
||||
}
|
||||
|
||||
private void clearConnection() {
|
||||
webSocketClient = null;
|
||||
webSocketClientCallback = null;
|
||||
}
|
||||
|
||||
private void reconnect() {
|
||||
parseLiveQueryClient.reconnect();
|
||||
webSocketClientCallback.onOpen();
|
||||
try {
|
||||
webSocketClientCallback.onMessage(createConnectedMessage().toString());
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static JSONObject createConnectedMessage() throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("op", "connected");
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
private static JSONObject createSubscribedMessage(int requestId) throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("op", "subscribed");
|
||||
jsonObject.put("clientId", 1);
|
||||
jsonObject.put("requestId", requestId);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
private static JSONObject createUnsubscribedMessage(int requestId) throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("op", "unsubscribed");
|
||||
jsonObject.put("requestId", requestId);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
private static JSONObject createErrorMessage(int requestId) throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("op", "error");
|
||||
jsonObject.put("requestId", requestId);
|
||||
jsonObject.put("code", 1);
|
||||
jsonObject.put("error", "testError");
|
||||
jsonObject.put("reconnect", true);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
private static JSONObject createObjectCreateMessage(int requestId, ParseObject parseObject) throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("op", "create");
|
||||
jsonObject.put("requestId", requestId);
|
||||
jsonObject.put("object", PointerEncoder.get().encodeRelatedObject(parseObject));
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
private static JSONObject createObjectEnterMessage(int requestId, ParseObject parseObject) throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("op", "enter");
|
||||
jsonObject.put("requestId", requestId);
|
||||
jsonObject.put("object", PointerEncoder.get().encodeRelatedObject(parseObject));
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
private static JSONObject createObjectUpdateMessage(int requestId, ParseObject parseObject) throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("op", "update");
|
||||
jsonObject.put("requestId", requestId);
|
||||
jsonObject.put("object", PointerEncoder.get().encodeRelatedObject(parseObject));
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
private static JSONObject createObjectLeaveMessage(int requestId, ParseObject parseObject) throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("op", "leave");
|
||||
jsonObject.put("requestId", requestId);
|
||||
jsonObject.put("object", PointerEncoder.get().encodeRelatedObject(parseObject));
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
private static JSONObject createObjectDeleteMessage(int requestId, ParseObject parseObject) throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("op", "delete");
|
||||
jsonObject.put("requestId", requestId);
|
||||
jsonObject.put("object", PointerEncoder.get().encodeRelatedObject(parseObject));
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
private static class LoggingCallbacks implements ParseLiveQueryClientCallbacks {
|
||||
final Transcript transcript = new Transcript();
|
||||
|
||||
@Override
|
||||
public void onLiveQueryClientConnected(ParseLiveQueryClient client) {
|
||||
transcript.add("onLiveQueryClientConnected");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLiveQueryClientDisconnected(ParseLiveQueryClient client, boolean userInitiated) {
|
||||
transcript.add("onLiveQueryClientDisconnected: " + userInitiated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLiveQueryError(ParseLiveQueryClient client, LiveQueryException reason) {
|
||||
transcript.add("onLiveQueryError: " + reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSocketError(ParseLiveQueryClient client, Throwable reason) {
|
||||
transcript.add("onSocketError: " + reason);
|
||||
}
|
||||
}
|
||||
|
||||
@ParseClassName("MockA")
|
||||
static class MockClassA extends ParseObject {
|
||||
}
|
||||
|
||||
@ParseClassName("MockB")
|
||||
static class MockClassB extends ParseObject {
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user