001package com.gigya.android.sdk.api; 002 003import com.gigya.android.sdk.Config; 004import com.gigya.android.sdk.Gigya; 005import com.gigya.android.sdk.GigyaDefinitions; 006import com.gigya.android.sdk.GigyaLogger; 007import com.gigya.android.sdk.api.models.GigyaConfigModel; 008import com.gigya.android.sdk.network.GigyaError; 009import com.gigya.android.sdk.network.adapter.IRestAdapter; 010import com.gigya.android.sdk.network.adapter.IRestAdapterCallback; 011import com.gigya.android.sdk.network.adapter.RestAdapter; 012 013import java.text.SimpleDateFormat; 014import java.util.Date; 015import java.util.HashMap; 016import java.util.Locale; 017import java.util.Map; 018 019/** 020 * Service responsible for mediating and executing HTTP based api requests. 021 */ 022public class ApiService implements IApiService { 023 024 private static final String LOG_TAG = "ApiService"; 025 026 final private Config _config; 027 final private IRestAdapter _adapter; 028 final private IApiRequestFactory _reqFactory; 029 030 public ApiService(Config config, 031 IRestAdapter adapter, 032 IApiRequestFactory reqFactory) { 033 _config = config; 034 _adapter = adapter; 035 _reqFactory = reqFactory; 036 } 037 038 /* 039 Main service comm interface. 040 */ 041 public interface IApiServiceResponse { 042 043 void onApiSuccess(GigyaApiResponse response); 044 045 void onApiError(GigyaError gigyaError); 046 } 047 048 private static final String SERVER_TIMESTAMP_PATTERN = "EEE, dd MMM yyyy HH:mm:ss zzz"; 049 050 /** 051 * Update main SDK interface with the current server offset value. 052 * 053 * @param dateHeader String date header field returned from last request. 054 */ 055 private void updateOffset(String dateHeader) { 056 if (dateHeader != null) { 057 try { 058 SimpleDateFormat format = new SimpleDateFormat(SERVER_TIMESTAMP_PATTERN, Locale.ENGLISH); 059 Date serverDate = format.parse(dateHeader); 060 Long offset = (serverDate.getTime() - System.currentTimeMillis()) / 1000; 061 062 GigyaLogger.debug(LOG_TAG, "updateOffset: Server timestamp = " + offset); 063 064 _config.setServerOffset(offset); 065 } catch (Exception ex) { 066 067 GigyaLogger.error(LOG_TAG, "updateOffset: unable to update offset with exception"); 068 ex.printStackTrace(); 069 } 070 } 071 } 072 073 @Override 074 public void send(final GigyaApiRequest request, boolean blocking, final IApiServiceResponse apiCallback) { 075 if (!request.getApi().equals(GigyaDefinitions.API.API_GET_SDK_CONFIG) && requiresSdkConfig()) { 076 // Need to verify if GMID is available. If not we must request SDK configuration. 077 getSdkConfig(apiCallback, request.getTag()); 078 } 079 080 GigyaLogger.debug(LOG_TAG, "sending: " + request.getApi()); 081 GigyaLogger.debug(LOG_TAG, "sending: params = " + request.getParams().toString()); 082 083 _adapter.send(request, blocking, new IRestAdapterCallback() { 084 @Override 085 public void onResponse(String jsonResponse, String responseDateHeader) { 086 087 updateOffset(responseDateHeader); 088 089 final GigyaApiResponse apiResponse = new GigyaApiResponse(jsonResponse); 090 final int apiErrorCode = apiResponse.getErrorCode(); 091 092 // Check for timestamp skew error. 093 if (isRequestExpiredError(apiErrorCode)) { 094 095 GigyaLogger.error(LOG_TAG, "Request expired error occurred. Allowing retries"); 096 097 new RetryDispatcher.Builder(_adapter) 098 .request(request) 099 .errorCode(GigyaError.Codes.ERROR_REQUEST_HAS_EXPIRED) 100 .tries(2) 101 .handler(new RetryDispatcher.IRetryHandler() { 102 @Override 103 public void onCompleteWithResponse(GigyaApiResponse retryResponse) { 104 apiCallback.onApiSuccess(retryResponse); 105 } 106 107 @Override 108 public void onCompleteWithError(GigyaError error) { 109 apiCallback.onApiError(error); 110 } 111 112 @Override 113 public void onUpdateDate(String date) { 114 updateOffset(date); 115 } 116 }) 117 .dispatch(); 118 return; 119 } 120 121 apiCallback.onApiSuccess(apiResponse); 122 } 123 124 @Override 125 public void onError(GigyaError gigyaError) { 126 apiCallback.onApiError(gigyaError); 127 } 128 129 }); 130 } 131 132 @Override 133 public void release() { 134 _adapter.release(); 135 } 136 137 @Override 138 public void cancel(String tag) { 139 _adapter.cancel(tag); 140 } 141 142 143 //region SDK CONFIG 144 145 private boolean requiresSdkConfig() { 146 return (_config.getGmid() == null || _config.getServerOffset() == null); 147 } 148 149 private void onConfigResponse(GigyaConfigModel response) { 150 _config.setUcid(response.getIds().getUcid()); 151 _config.setGmid(response.getIds().getGmid()); 152 release(); 153 } 154 155 private void onConfigError(String nextApiTag) { 156 if (nextApiTag != null) { 157 cancel(nextApiTag); 158 } 159 release(); 160 } 161 162 private void getSdkConfig(final IApiServiceResponse apiCallback, final String nextApiTag) { 163 164 GigyaLogger.debug(LOG_TAG, "sending: " + GigyaDefinitions.API.API_GET_SDK_CONFIG); 165 166 final Map<String, Object> params = new HashMap<>(); 167 params.put("include", "permissions,ids,appIds"); 168 final GigyaApiRequest request = _reqFactory.create( 169 GigyaDefinitions.API.API_GET_SDK_CONFIG, 170 params, 171 RestAdapter.HttpMethod.GET); 172 // Set request as anonymous! Will not go through if will include timestamp, nonce & signature. 173 request.setAnonymous(true); 174 send(request, true, new IApiServiceResponse() { 175 @Override 176 public void onApiSuccess(GigyaApiResponse apiResponse) { 177 final int apiErrorCode = apiResponse.getErrorCode(); 178 if (apiErrorCode == 0) { 179 final GigyaConfigModel parsed = apiResponse.parseTo(GigyaConfigModel.class); 180 if (parsed == null) { 181 // Parsing error. 182 apiCallback.onApiError(GigyaError.fromResponse(apiResponse)); 183 onConfigError(nextApiTag); 184 return; 185 } 186 onConfigResponse(parsed); 187 } else { 188 apiCallback.onApiError(GigyaError.fromResponse(apiResponse)); 189 onConfigError(nextApiTag); 190 } 191 } 192 193 @Override 194 public void onApiError(GigyaError gigyaError) { 195 apiCallback.onApiError(gigyaError); 196 onConfigError(nextApiTag); 197 } 198 }); 199 } 200 201 //endregion 202 203 private boolean isRequestExpiredError(int code) { 204 return code == GigyaError.Codes.ERROR_REQUEST_HAS_EXPIRED; 205 } 206 207}