package com.tresorit.zerokit; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.test.espresso.IdlingResource; import com.google.gson.Gson; import com.tresorit.zerokit.call.Action; import com.tresorit.zerokit.call.Call; import com.tresorit.zerokit.call.CallBase; import com.tresorit.zerokit.call.Callback; import com.tresorit.zerokit.call.Response; import com.tresorit.zerokit.response.ResponseAdminApiError; import com.tresorit.zerokit.response.ResponseAdminApiInitUserRegistration; import com.tresorit.zerokit.response.ResponseAdminApiLoginByCode; import com.tresorit.zerokit.util.Holder; import com.zerokit.zerokit.BuildConfig; import org.json.JSONObject; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.ResponseBody; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.http.Body; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.Headers; import retrofit2.http.POST; import retrofit2.http.Query; public class AdminApi { private static final String AUTHORIZATION = "Authorization"; private static final String AUTHORIZATION_HEADER = AUTHORIZATION + ": Bearer %s"; @SuppressWarnings("WeakerAccess") final AdminApiService adminApiService; private String clientId; @SuppressWarnings("WeakerAccess") String token; @SuppressWarnings("WeakerAccess") final Gson gson; @SuppressWarnings("WeakerAccess") static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); @SuppressWarnings("WeakerAccess") final Executor executorBackground; @SuppressWarnings("WeakerAccess") @Nullable ZerokitCountingIdlingResource idlingResource; public AdminApi(String host, String clientId) { this.clientId = clientId; OkHttpClient.Builder builderHttpClient = new OkHttpClient.Builder(); HttpLoggingInterceptor interceptorLogging = new HttpLoggingInterceptor(); interceptorLogging.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.NONE); Interceptor interceptorAuthorization = new Interceptor() { @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request request = chain.request(); String authHeader = request.headers().get(AUTHORIZATION); if (authHeader != null) request = request.newBuilder().removeHeader(AUTHORIZATION).addHeader(AUTHORIZATION, String.format(authHeader, token)).build(); return chain.proceed(request); } }; builderHttpClient.addInterceptor(interceptorAuthorization); builderHttpClient.addInterceptor(interceptorLogging); Retrofit.Builder builder = new Retrofit.Builder().baseUrl(host).client(builderHttpClient.build()).addConverterFactory(GsonConverterFactory.create()); Retrofit retrofit = builder.build(); adminApiService = retrofit.create(AdminApiService.class); gson = new Gson(); executorBackground = Executors.newSingleThreadExecutor(); } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public void clearClientId() { setClientId(null); } public void setToken(String token) { this.token = token; } public void clearToken() { setToken(null); } public Call<String, ResponseAdminApiError> getUserId(final String userName) { return new CallRetrofit<>(adminApiService.getUserId(userName)); } public Call<ResponseAdminApiInitUserRegistration, ResponseAdminApiError> initReg(final String userName, final String profileData) { return new CallRetrofit<>(adminApiService.initReg(userName, profileData)); } public Call<Void, ResponseAdminApiError> finishReg(final String userId, final String validationVerifier) { return new CallRetrofit<>(adminApiService.finishReg(userId, validationVerifier)); } public Call<String, ResponseAdminApiError> validateUser(final String userId, final String validationCode) { return new CallRetrofit<>(adminApiService.validateUser(userId, validationCode)); } public Call<String, ResponseAdminApiError> setProfile(final String json) { return new CallRetrofit<>(adminApiService.setProfile(RequestBody.create(JSON, json))); } public Call<String, ResponseAdminApiError> getProfile() { return new CallRetrofit<>(adminApiService.getProfile()); } public Call<ResponseAdminApiLoginByCode, ResponseAdminApiError> login(final String code) { return new CallRetrofit<>(adminApiService.login(clientId, code)); } public Call<Void, ResponseAdminApiError> createdTresor(final String tresorId) { return new CallRetrofit<>(adminApiService.createdTresor(tresorId)); } public Call<Void, ResponseAdminApiError> sharedTresor(final String operationId) { return new CallRetrofit<>(adminApiService.sharedTresor(operationId)); } public Call<Void, ResponseAdminApiError> kickedUser(final String operationId) { return new CallRetrofit<>(adminApiService.kickedUser(operationId)); } @SuppressWarnings("WeakerAccess") public Call<String, ResponseAdminApiError> storeData(final String tresorId, final String id, final String json) { return new CallRetrofit<>(adminApiService.storeData(tresorId, id, RequestBody.create(JSON, json))); } public Call<String, ResponseAdminApiError> storeData(final String tresorId, final String id, final JSONObject json) { return storeData(tresorId, id, json.toString()); } @SuppressWarnings("WeakerAccess") public Call<String, ResponseAdminApiError> fetchData(final String id) { return new CallRetrofit<>(adminApiService.fetchData(id)); } public <T> Call<T, ResponseAdminApiError> fetchData(final String id, final Class<T> clazz) { return new CallBase<T, ResponseAdminApiError>() { @Override public void enqueue(final Callback<? super T, ? super ResponseAdminApiError> callback) { fetchData(id).enqueue(new Action<String>() { @Override public void call(String s) { callback.onSuccess(gson.fromJson(s, clazz)); } }, new Action<ResponseAdminApiError>() { @Override public void call(ResponseAdminApiError responseAdminApiError) { callback.onError(responseAdminApiError); } }); } @Override public Response<T, ResponseAdminApiError> execute() { Response<String, ResponseAdminApiError> execute = fetchData(id).execute(); return Response.from(gson.fromJson(execute.getResult(), clazz), execute.getError()); } }; } private class CallRetrofit<T> extends CallBase<T, ResponseAdminApiError> { final retrofit2.Call<T> call; CallRetrofit(retrofit2.Call<T> call) { this.call = call; } @Override public void enqueue(Callback<? super T, ? super ResponseAdminApiError> callback) { incrementIdlingResource(); call.enqueue(new CallbackRetrofit<>(callback)); } @Override public Response<T, ResponseAdminApiError> execute() { incrementIdlingResource(); final Holder<Response<T, ResponseAdminApiError>> result = new Holder<>(); final CountDownLatch signal = new CountDownLatch(1); executorBackground.execute(new Runnable() { @Override public void run() { try { result.t = getResponseFromResponse(call.execute()); } catch (IOException e) { result.t = Response.fromError(new ResponseAdminApiError(e.getMessage())); } signal.countDown(); } }); if (signal.getCount() > 0) try { signal.await(); } catch (InterruptedException e) { e.printStackTrace(); } return result.t; } } @SuppressWarnings("WeakerAccess") void incrementIdlingResource() { if (idlingResource != null) idlingResource.increment(); } @SuppressWarnings("WeakerAccess") void decrementIdlingResource() { if (idlingResource != null) idlingResource.decrement(); } private class CallbackRetrofit<T> implements retrofit2.Callback<T> { private final Callback<? super T, ? super ResponseAdminApiError> callback; CallbackRetrofit(Callback<? super T, ? super ResponseAdminApiError> callback) { this.callback = callback; } @Override public void onResponse(retrofit2.Call<T> call, retrofit2.Response<T> response) { decrementIdlingResource(); Response<? extends T, ? extends ResponseAdminApiError> responseFromResponse = getResponseFromResponse(response); if (responseFromResponse.isError()) callback.onError(responseFromResponse.getError()); else callback.onSuccess(responseFromResponse.getResult()); } @Override public void onFailure(retrofit2.Call<T> call, Throwable t) { decrementIdlingResource(); callback.onError(new ResponseAdminApiError(t.getMessage())); } } @SuppressWarnings("WeakerAccess") <T> Response<T, ResponseAdminApiError> getResponseFromResponse(retrofit2.Response<T> response) { Response<T, ResponseAdminApiError> result; if (response.isSuccessful()) result = Response.fromValue(response.body()); else { try { ResponseBody responseBody = response.errorBody(); if (responseBody.contentType() != null && "json".equals(responseBody.contentType().subtype())) result = Response.fromError(gson.fromJson(new String(responseBody.bytes()), ResponseAdminApiError.class)); else result = Response.fromError(new ResponseAdminApiError(new String(responseBody.bytes()))); } catch (IOException e) { result = Response.fromError(new ResponseAdminApiError(e.getMessage())); } } return result; } private interface AdminApiService { @GET("/api/user/get-user-id") retrofit2.Call<String> getUserId(@Query("userName") String userName); @FormUrlEncoded @POST("/api/user/init-user-registration") retrofit2.Call<ResponseAdminApiInitUserRegistration> initReg(@Field("userName") String userName, @Field("profileData") String profileData); @FormUrlEncoded @POST("/api/user/finish-user-registration") retrofit2.Call<Void> finishReg(@Field("userId") String userId, @Field("validationVerifier") String validationVerifier); @FormUrlEncoded @POST("/api/user/validate-user") retrofit2.Call<String> validateUser(@Field("userId") String userId, @Field("validationCode") String validationCode); @FormUrlEncoded @POST("/api/auth/login-by-code?token=true") retrofit2.Call<ResponseAdminApiLoginByCode> login(@Query("clientId") String clientId, @Field("code") String code); @FormUrlEncoded @Headers(AUTHORIZATION_HEADER) @POST("/api/tresor/created") retrofit2.Call<Void> createdTresor(@Field("tresorId") String tresorId); @FormUrlEncoded @Headers(AUTHORIZATION_HEADER) @POST("/api/tresor/invited-user") retrofit2.Call<Void> sharedTresor(@Field("operationId") String operationId); @FormUrlEncoded @Headers(AUTHORIZATION_HEADER) @POST("/api/tresor/kicked-user") retrofit2.Call<Void> kickedUser(@Field("operationId") String operationId); @Headers(AUTHORIZATION_HEADER) @POST("/api/data/store") retrofit2.Call<String> storeData(@Query("tresorId") String tresorId, @Query("id") String id, @Body RequestBody data); @Headers(AUTHORIZATION_HEADER) @GET("/api/data/get") retrofit2.Call<String> fetchData(@Query("id") String id); @Headers(AUTHORIZATION_HEADER) @POST("/api/data/profile") retrofit2.Call<String> setProfile(@Body RequestBody data); @Headers(AUTHORIZATION_HEADER) @GET("/api/data/profile") retrofit2.Call<String> getProfile(); } private class ZerokitCountingIdlingResource implements IdlingResource { private final AtomicInteger counter; private volatile ResourceCallback resourceCallback; ZerokitCountingIdlingResource() { this(0); } ZerokitCountingIdlingResource(int initialCount) { this.counter = new AtomicInteger(initialCount); } @Override public String getName() { return getClass().getName(); } @Override public boolean isIdleNow() { return counter.get() == 0; } @Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { this.resourceCallback = resourceCallback; } void increment() { counter.getAndIncrement(); } void decrement() { int counterVal = counter.decrementAndGet(); if (counterVal == 0) { if (null != resourceCallback) { resourceCallback.onTransitionToIdle(); } } if (counterVal < 0) { throw new IllegalArgumentException("Counter has been corrupted!"); } } } @VisibleForTesting @NonNull public IdlingResource getIdlingResource() { if (idlingResource == null) idlingResource = new ZerokitCountingIdlingResource(); return idlingResource; } }