package com.instamojo.wrapper.api; import com.google.gson.Gson; import com.instamojo.wrapper.exception.ConnectionException; import com.instamojo.wrapper.exception.HTTPException; import com.instamojo.wrapper.exception.InvalidClientException; import com.instamojo.wrapper.model.AccessToken; import com.instamojo.wrapper.util.Constants; import com.instamojo.wrapper.util.HttpUtils; import org.apache.http.util.TextUtils; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; /** * The Class ApiContext. */ public class ApiContext { private static final Logger LOGGER = Logger.getLogger(ApiContext.class.getName()); public enum Mode { TEST, LIVE } private volatile static ApiContext instance; /* * The access token related info */ private AccessToken accessToken; /* * The token creation time */ private long tokenCreationTime; /* * Instamojo client id */ private String clientId; /* * The client secret */ private String clientSecret; private String username; private String password; private Mode mode; private ApiContext(String clientId, String clientSecret, Mode mode) { this(clientId, clientSecret, null, null, mode); } private ApiContext(String clientId, String clientSecret, String username, String password, Mode mode) { this.clientId = clientId; this.clientSecret = clientSecret; this.mode = mode; this.username = username; this.password = password; } /** * Create a context * * @param clientId the client id * @param clientSecret the client secret * @param mode TEST or LIVE * @return the api */ public static ApiContext create(String clientId, String clientSecret, Mode mode) { if (instance == null) { synchronized (ApiContext.class) { if (instance == null) { instance = new ApiContext(clientId, clientSecret, mode); } } } return instance; } /** * Create a context * * @param clientId the client id * @param clientSecret the client secret * @param username user's username * @param password user's password * @param mode TEST or LIVE * @return the api */ public static ApiContext create(String clientId, String clientSecret, String username, String password, Mode mode) { if (instance == null) { synchronized (ApiContext.class) { if (instance == null) { instance = new ApiContext(clientId, clientSecret, username, password, mode); } } } return instance; } private boolean isTokenExpired() { long durationInSeconds = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - tokenCreationTime); return durationInSeconds >= (accessToken.getExpiresIn() - 300); } private void loadOrRefreshAccessToken() throws ConnectionException, HTTPException { if (accessToken == null) { fetchAccessToken(); } else if (isTokenExpired()) { refreshAccessToken(); } } /* * Fetch a new access token. */ private synchronized void fetchAccessToken() throws ConnectionException, HTTPException { if (accessToken != null) { return; } Map<String, String> params = new HashMap<>(); params.put(Constants.PARAM_CLIENT_ID, clientId); params.put(Constants.PARAM_CLIENT_SECRET, clientSecret); if (isUserAuthFlow()) { params.put(Constants.PARAM_GRANT_TYPE, Constants.PARAM_PASSWORD); params.put(Constants.PARAM_USERNAME, username); params.put(Constants.PARAM_PASSWORD, password); } else { params.put(Constants.PARAM_GRANT_TYPE, Constants.GRANT_TYPE_CLIENT_CREDENTIALS); } loadAccessToken(params); } /* * Refresh an expired access token */ private synchronized void refreshAccessToken() throws ConnectionException, HTTPException { if (!isTokenExpired()) { return; } Map<String, String> params = new HashMap<>(); params.put(Constants.PARAM_CLIENT_ID, clientId); params.put(Constants.PARAM_CLIENT_SECRET, clientSecret); if (isUserAuthFlow() && accessToken.getRefreshToken() != null) { // For user based authentication, refresh the token params.put(Constants.PARAM_GRANT_TYPE, Constants.GRANT_TYPE_REFRESH_TOKEN); params.put(Constants.PARAM_REFRESH_TOKEN, accessToken.getRefreshToken()); } else { // For app based authentication, reload the token params.put(Constants.PARAM_GRANT_TYPE, Constants.GRANT_TYPE_CLIENT_CREDENTIALS); } loadAccessToken(params); } private void loadAccessToken(Map<String, String> params) throws ConnectionException, HTTPException { try { String response = HttpUtils.post(getAuthEndpoint(), null, params); AccessToken accessTokenResponse = new Gson().fromJson(response, AccessToken.class); if (TextUtils.isEmpty(accessTokenResponse.getToken())) { throw new InvalidClientException( "Could not get the access token due to " + accessTokenResponse.getError()); } this.accessToken = accessTokenResponse; this.tokenCreationTime = System.nanoTime(); } catch (IOException e) { LOGGER.log(Level.SEVERE, e.toString(), e); throw new ConnectionException(e.toString(), e); } } /** * Clears the current cached Instance */ public static void ClearInstance() { if (instance != null) { synchronized (ApiContext.class) { instance = null; } } } /* * Gets the authorization. */ public String getAuthorization() throws ConnectionException, HTTPException { loadOrRefreshAccessToken(); return accessToken.getTokenType() + " " + accessToken.getToken(); } /* * Gets the api path. */ public String getApiPath(String path) { String apiPath = getApiEndpoint() + path; if (!apiPath.endsWith("/")) { apiPath += Character.toString('/'); } return apiPath; } private String getApiEndpoint() { if (mode == Mode.TEST) { return Constants.INSTAMOJO_TEST_API_ENDPOINT; } return Constants.INSTAMOJO_LIVE_API_ENDPOINT; } private String getAuthEndpoint() { if (mode == Mode.TEST) { return Constants.INSTAMOJO_TEST_AUTH_ENDPOINT; } return Constants.INSTAMOJO_LIVE_AUTH_ENDPOINT; } private boolean isUserAuthFlow() { return (username != null && password != null); } }