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}