package com.badoo.chateau.example.data.util; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.util.Log; import com.parse.ParseCloud; import com.parse.ParseException; import com.parse.ParseObject; import com.parse.ParseQuery; import com.parse.ParseUser; import com.parse.SaveCallback; import java.util.List; import java.util.Map; import rx.Observable; import rx.Subscriber; public class ParseHelper { public static final ParseHelper INSTANCE = new ParseHelper(); private static final String TAG = ParseHelper.class.getSimpleName(); private static final boolean DEBUG = true; @VisibleForTesting ParseHelper() { } public ParseUser getCurrentUser() { return ParseUser.getCurrentUser(); } public <T> Observable<T> callFunction(@NonNull String functionName, @NonNull Map<String, ?> params) { Log.d(TAG, "Calling function: " + functionName + ", with params: " + params); return Observable.create(new SimpleParseRequest<T>("Exception while calling method " + functionName) { @NonNull @Override protected T networkCall() throws ParseException { return ParseCloud.callFunction(functionName, params); } }); } public <T> Observable<T> callFunctionForList(@NonNull String functionName, @NonNull Map<String, ?> params) { Log.d(TAG, "Calling function: " + functionName + ", with params: " + params); return Observable.create(new ParseRequest<List<T>, T>("Exception while calling method " + functionName) { @NonNull @Override protected List<T> networkCall() throws ParseException { return ParseCloud.callFunction(functionName, params); } @Override protected void publishResult(@NonNull Subscriber<? super T> subscriber, @NonNull List<T> results) { for (T result : results) { subscriber.onNext(result); } } }); } public <T extends ParseObject> Observable<List<T>> find(@NonNull ParseQuery<T> query) { Log.d(TAG, "Running query for: " + query.getClassName()); return Observable.create(new SimpleParseRequest<List<T>>("Exception while executing find() for class " + query.getClassName()) { @NonNull @Override protected List<T> networkCall() throws ParseException { return query.find(); } }); } public <T extends ParseObject> Observable<T> get(@NonNull ParseQuery<T> query, @NonNull String objectId) { Log.d(TAG, "Running query for: " + query.getClassName()); return Observable.create(new SimpleParseRequest<T>("Exception while executing get() for class " + query.getClassName()) { @NonNull @Override protected T networkCall() throws ParseException { return query.get(objectId); } }); } public Observable<ParseUser> signIn(@NonNull String userName, @NonNull String password) { Log.d(TAG, "Running sign in"); return Observable.create(new SimpleParseRequest<ParseUser>("Exception while calling signIn") { @NonNull @Override protected ParseUser networkCall() throws ParseException { return ParseUser.logIn(userName, password); } }); } public Observable<Void> signOut() { Log.d(TAG, "Running sign out"); return Observable.create(new SimpleParseRequest<Void>("Exception while calling signOut") { @NonNull @Override protected Void networkCall() throws ParseException { ParseUser.logOut(); return null; } }); } public Observable<ParseUser> signUp(@NonNull String userName, @NonNull String password, @NonNull Map<String, ?> params) { Log.d(TAG, "Running sign up"); return Observable.create(new SimpleParseRequest<ParseUser>("Exception while calling signUp") { @NonNull @Override protected ParseUser networkCall() throws ParseException { final ParseUser user = new ParseUser(); user.setUsername(userName); user.setPassword(password); for (Map.Entry<String, ?> param : params.entrySet()) { user.put(param.getKey(), param.getValue()); } user.signUp(); return user; } }); } public void saveInBackground(@NonNull ParseObject msg, @Nullable SaveCallback callback) { msg.saveInBackground(callback); } public void save(@NonNull ParseObject msg) { try { msg.save(); } catch (ParseException e) { throw new RuntimeException(e); } } /** * Simple implementation of ParseRequest where the result of the network call is published to the subscriber directly. */ private abstract class SimpleParseRequest<T> extends ParseRequest<T, T> { SimpleParseRequest(@Nullable String traceMessage) { super(traceMessage); } @Override protected void publishResult(@NonNull Subscriber<? super T> subscriber, @NonNull T result) { subscriber.onNext(result); } } /** * Abstract implementation of {@link OnSubscribe} to ensure that for a given request no matter the number of subscriptions made the * network call will only be executed once. Subsequent subscriptions will either wait for an existing network call to complete or * just receive the result. */ private abstract class ParseRequest<R, T> implements Observable.OnSubscribe<T> { private final RuntimeException mTraceException; private volatile boolean mCallCompleted = false; private R mResult; private ParseException mParseException; /** * If using in debug mode, a suppressed exception will be added to the error published via the subscriber to assist with finding the * cause. */ ParseRequest(@Nullable String traceMessage) { //noinspection PointlessBooleanExpression mTraceException = DEBUG && traceMessage != null ? new RuntimeException(traceMessage) : null; } @Override public void call(Subscriber<? super T> subscriber) { if (!mCallCompleted) { synchronized (this) { if (!mCallCompleted) { try { mResult = networkCall(); } catch (ParseException e) { mParseException = e; } mCallCompleted = true; } } } if (mResult != null) { publishResult(subscriber, mResult); } else if (mParseException != null) { publishError(subscriber, mParseException); return; } // As we might not have a result for example where the return type is void, onCompleted needs to occur outside of the result // if statement. subscriber.onCompleted(); } @NonNull protected abstract R networkCall() throws ParseException; protected abstract void publishResult(@NonNull Subscriber<? super T> subscriber, @NonNull R result); protected void publishError(@NonNull Subscriber<? super T> subscriber, @NonNull ParseException exception) { if (mTraceException != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { exception.addSuppressed(mTraceException); } subscriber.onError(mParseException); } } }