/*
* Licensed Materials - Property of IBM
* © Copyright IBM Corporation 2015. All Rights Reserved.
*/
package com.ibm.mil.cafejava;
import android.content.Context;
import android.support.annotation.NonNull;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import com.worklight.wlclient.api.WLClient;
import com.worklight.wlclient.api.WLFailResponse;
import com.worklight.wlclient.api.WLResponse;
import com.worklight.wlclient.api.WLResponseListener;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Func1;
import static rx.Observable.Transformer;
/**
* Collection of methods for dealing with MFP procedure invocations in a reactive manner. For a
* detailed guide on using the methods in this class, visit
* <a href="https://github.com/t-preiss/CafeJava" target="_blank">the project's GitHub page</a> and
* view the README.
*
* @author John Petitto (github @jpetitto)
* @author Tanner Preiss (github @t-preiss)
*/
public final class CafeJava {
private CafeJava() {
throw new AssertionError(CafeJava.class.getName() + " is non-instantiable");
}
/**
* Creates an {@code Observable} that emits a {@code WLResponse} after attempting connection
* to the MFP server instance defined in the {@code wlclient.properties} file. This
* connection is only performed when there is a new {@code Subscriber} to the {@code
* Observable}. The {@code Observable} will automatically perform its work on a dedicated
* background thread, so there is usually no need to use the {@code subscribeOn} method of
* RxJava.
*
* @param context
* @return {@code Observable} that emits a {@code WLResponse} for an MFP connection.
*/
@NonNull
public static Observable<WLResponse> connect(@NonNull final Context context) {
return Observable.create(new Observable.OnSubscribe<WLResponse>() {
@Override
public void call(Subscriber<? super WLResponse> subscriber) {
WLClient client = WLClient.createInstance(context);
client.connect(new RxResponseListener(subscriber));
}
});
}
/**
* Creates an {@code Observable} that emits a {@code WLResponse} after attempting to invoke
* the {@code ProcedureInvoker} parameter that is passed in. This invocation is only
* performed when there is a new {@code Subscriber} to the {@code Observable}. The {@code
* Observable} will automatically perform its work on a dedicated background thread, so there
* is usually no need to use the {@code subscribeOn} method of RxJava.
*
* @param invoker Implementation for a procedure invocation, most commonly
* {@link JavaProcedureInvoker} or {@link JSProcedureInvoker}.
* @return {@code Observable} that emits a {@code WLResponse} for an MFP procedure invocation.
*/
@NonNull
public static Observable<WLResponse> invokeProcedure(@NonNull final ProcedureInvoker invoker) {
return Observable.create(new Observable.OnSubscribe<WLResponse>() {
@Override public void call(Subscriber<? super WLResponse> subscriber) {
invoker.invoke(new RxResponseListener(subscriber));
}
});
}
/**
* Transforms an {@code Observable} that emits a {@code WLResponse} with a valid JSON payload
* into a new {@code Observable} with the targeted {@code Class} literal. This can be done by
* passing the result of this method to the {@code compose} operator of RxJava. A variable
* number of member names can be provided for accessing JSON data that is nested arbitrarily
* deep inside the response payload.
*
* @param clazz Targeted {@code Class} for the JSON payload to be serialized into.
* @param memberNames Variable number of member names for accessing JSON data that is nested
* arbitrarily deep inside the response payload.
* @return {@code Transformer} that can be supplied to the {@code compose} operator of RxJava
* . The input {@code Observable} must emit a {@code WLResponse} with a valid JSON payload.
*/
@NonNull
public static <T> Transformer<WLResponse, T> serializeTo(@NonNull final Class<T> clazz,
@NonNull final String... memberNames) {
return transformJson(new Func1<WLResponse, T>() {
@Override
public T call(WLResponse wlResponse) {
JsonElement element = parseNestedJson(wlResponse, memberNames);
return new Gson().fromJson(element, clazz);
}
});
}
/**
* Transforms an {@code Observable} that emits a {@code WLResponse} with a valid JSON payload
* into a new Observable for the targeted {@code TypeToken}. This can be done by passing the
* result of this method to the {@code compose} operator of RxJava. A {@code TypeToken} is
* necessary when the targeted type is a parameterized type, such as {@code List}. A variable
* number of member names can be provided for accessing JSON data that is nested arbitrarily
* deep inside the response payload.
*
* @param typeToken Captures the necessary type information for the targeted parameterized
* type, such as {@code List}.
* @param memberNames Variable number of member names for accessing JSON data that is nested
* arbitrarily deep inside the response payload.
* @return {@code Transformer} that can be supplied to the {@code compose} operator of RxJava
* . The input {@code Observable} must emit a {@code WLResponse} with a valid JSON payload.
*/
@NonNull
public static <T> Transformer<WLResponse, T> serializeTo(@NonNull final TypeToken<T> typeToken,
@NonNull final String... memberNames) {
return transformJson(new Func1<WLResponse, T>() {
@Override
public T call(WLResponse wlResponse) {
JsonElement element = parseNestedJson(wlResponse, memberNames);
return new Gson().fromJson(element, typeToken.getType());
}
});
}
private static <T> Transformer<WLResponse, T> transformJson(final Func1<WLResponse, T> func) {
return new Transformer<WLResponse, T>() {
@Override
public Observable<T> call(Observable<WLResponse> wlResponseObservable) {
return wlResponseObservable.map(func);
}
};
}
private static JsonElement parseNestedJson(WLResponse wlResponse, String... memberNames) {
String json = wlResponse.getResponseJSON().toString();
JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject();
// For each member name, fetch the object it maps to until you reach the final member name.
// Once the final member name is reached, return its corresponding value.
for (int i = 0, size = memberNames.length; i < size; i++) {
String member = memberNames[i];
if (i == size - 1) {
// last member name reached; return its value
return jsonObject.get(member);
} else {
// more member names remain, therefore current member must map to an object
jsonObject = jsonObject.getAsJsonObject(member);
}
}
// no nesting required; return top-level object
return jsonObject;
}
private static class RxResponseListener implements WLResponseListener {
private Subscriber<? super WLResponse> subscriber;
RxResponseListener(Subscriber<? super WLResponse> subscriber) {
this.subscriber = subscriber;
}
@Override
public void onSuccess(WLResponse wlResponse) {
subscriber.onNext(wlResponse);
subscriber.onCompleted();
}
@Override
public void onFailure(WLFailResponse wlFailResponse) {
subscriber.onError(new Throwable(wlFailResponse.getErrorCode() + ": " + wlFailResponse
.getErrorMsg()));
}
}
}