Java tutorial
/* * Copyright 2014 Ericsson AB * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.etalio.android; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import com.etalio.android.util.Log; import com.etalio.android.client.DefaultEtalioHttpClient; import com.etalio.android.client.GsonHttpBodyConverter; import com.etalio.android.client.HttpBodyConverter; import com.etalio.android.client.HttpClient; import com.etalio.android.client.HttpRequest; import com.etalio.android.client.HttpResponse; import com.etalio.android.client.exception.EtalioAuthorizationCodeException; import com.etalio.android.client.exception.EtalioHttpException; import com.etalio.android.client.exception.EtalioTokenException; import com.etalio.android.client.models.EtalioToken; import com.etalio.android.client.models.TokenResponse; import com.etalio.android.util.MimeTypeUtil; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.security.SecureRandom; import java.text.ParseException; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Random; /** * The base class wraps the Etalio authentication flow and contain methods to handle API calls. */ public abstract class EtalioBase implements EtalioPersistentStore { private static final String TAG = EtalioBase.class.getCanonicalName(); //Constants private static final String AB = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; private static final String UTF_8 = "UTF-8"; //Api base url public static final String API_URL = "https://api.etalio.com/"; protected static final String SDK_TAG = "etalio-android-sdk-"; //Request parameters protected static final String PARAM_CLIENT_ID = "client_id"; protected static final String PARAM_CLIENT_SECRET = "client_secret"; protected static final String PARAM_RESPONSE_TYPE = "response_type"; protected static final String PARAM_REDIRECT_URI = "redirect_uri"; protected static final String PARAM_GRANT_TYPE = "grant_type"; protected static final String PARAM_REFRESH_TOKEN = "refresh_token"; protected static final String PARAM_STATE = "state"; protected static final String PARAM_CODE = "code"; protected static final String PARAM_SCOPE = "scope"; protected static final String PARAM_SDK = "sdk"; protected static final String PARAM_ERROR = "error"; //Content types public static final String CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; public static final String CONTENT_TYPE_APPLICATION_JSON = "application/json"; //Redirect uri private static final String REDIRECT_URI_PROTOCOL_BASE = "etalio"; private static final String REDIRECT_URI_PATH = "authentication"; //Extras public static final String EXTRA_CLIENT_ID = "com.etalio.CLIENT_ID"; public static final String EXTRA_PACKAGE_NAME = "com.etalio.PACKAGE_NAME"; public static final String EXTRA_ACCESS_TOKEN = "com.etalio.ACCESS_TOKEN"; public static final String EXTRA_REFRESH_TOKEN = "com.etalio.REFRESH_TOKEN"; public static final String EXTRA_EXPIRES_IN = "com.etalio.EXPIRES_IN"; private static final int REQUEST_CODE_SSO = 10001; //Map for storing api endpoints protected final Map<String, String> domainMap = new HashMap<String, String>(); private final Context mContext; //Storage of Oauth2 values protected String mClientId, mClientSecret, mRedirectUri; private HttpClient mHttpClient; private HttpBodyConverter httpBodyConverter; /** * Constructor * * @param clientID the client identificator * @param clientSecret the client secret */ public EtalioBase(String clientID, String clientSecret, Context context) { this(clientID, clientSecret, context, API_URL); } public EtalioBase(String clientID, String clientSecret, Context context, String apiUrl) { if (Utils.isNullOrEmpty(clientID)) { throw new IllegalArgumentException("Client ID must not be null"); } if (Utils.isNullOrEmpty(clientSecret)) { throw new IllegalArgumentException("Client Secret must not be null"); } this.mClientId = clientID; this.mClientSecret = clientSecret; this.mRedirectUri = REDIRECT_URI_PROTOCOL_BASE + clientID + "://" + REDIRECT_URI_PATH; this.mContext = context; domainMap.put("oauth2", apiUrl + "oauth2"); domainMap.put("token", apiUrl + "oauth2/token"); domainMap.put("revoke", apiUrl + "oauth2/revoke"); } /** * Redirects the end user to Etalio sign in. Sign in will happen through the * Etalio app if installed, else through the web browser. * * <code>mEtalio.initiateEtalioSignIn(this, "profile.basic.r profile.email.r");</code> * * @param activity the activity used to call Etalio sign in. * @param scope scopes for the sign in. The scopes should be separated by spaces, like "profile.basic.r profile.email.r" */ public void initiateEtalioSignIn(Activity activity, String scope) { resetState(); if (!isEtalioInstalled(activity)) { activity.startActivity(new Intent(Intent.ACTION_VIEW, getSignInUrl(scope))); } else { Intent etalio = new Intent(); etalio.setClassName("com.etalio.android", "com.etalio.android.app.ui.activity.SingleSignOnActivity"); etalio.putExtra(EXTRA_CLIENT_ID, mClientId); etalio.putExtra(EXTRA_PACKAGE_NAME, activity.getPackageName()); etalio.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); activity.startActivityForResult(etalio, REQUEST_CODE_SSO); } } private boolean isEtalioInstalled(Context context) { PackageManager pm = context.getPackageManager(); try { pm.getPackageInfo("com.etalio.android", PackageManager.GET_ACTIVITIES); return true; } catch (PackageManager.NameNotFoundException e) { return false; } } /** * Redirects the end user to Etalio sign in with default "profile.r" scope. Sign in will happen through the Etalio app if installed, else through the web browser. * @param activity the activity used to call Etalio sign in. */ public void initiateEtalioSignIn(Activity activity) { initiateEtalioSignIn(activity, null); } /** * Request the access token given for this session to be revoked and not valid anymore. * @return true if the token was successfully revoked. If not an exception should be thrown. * @throws IOException If request fails due to a problem with IO. * @throws EtalioHttpException If the http request fails when revoking the token. */ public boolean requestRevokeAccess() throws IOException, EtalioHttpException { Map<String, String> params = new HashMap<String, String>(); params.put("token", getEtalioToken().getAccessToken()); params.put("token_type_hint", "access_token"); byte[] body = generateFormEncodedRequestBody(params); Map<String, String> headers = buildSignedApiCallHeaders(); addEtalioUserAgent(headers); HttpRequest request = new HttpRequest(HttpRequest.HttpMethod.POST, getUrl("revoke", null), headers, body, CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED, body != null ? body.length : 0); HttpResponse response = getHttpClient().executeRequest(request, getHttpBodyConverter(), String.class); return true; } /** * Checks the intent for data belonging to an Etalio Callback * * @param intent the intent * @return if the intent contains an Etalio Callback * @throws com.etalio.android.client.exception.EtalioAuthorizationCodeException if callback contains error parameter. */ protected boolean isEtalioSignInCallback(Intent intent) throws EtalioAuthorizationCodeException { if (intent != null && intent.getData() != null && Utils.matchUris(intent.getData(), Uri.parse(mRedirectUri))) { //Check state if (!getState().equals(intent.getData().getQueryParameter(PARAM_STATE))) { throw new EtalioAuthorizationCodeException( EtalioAuthorizationCodeException.AuthorizationCodeGrantErrorResponse.INVALID_STATE .getError()); } String code = getAuthorizationCodeFromUri(intent.getData()); if (Utils.isNullOrEmpty(code)) { //Code is missing look for fail callback String error = intent.getData().getQueryParameter(PARAM_ERROR); throw new EtalioAuthorizationCodeException(error); } return true; } return false; } /** * Get the authorization code parameter from a uri. * @param data The Uri to get the code from. * @return The code */ private String getAuthorizationCodeFromUri(Uri data) { return data.getQueryParameter(PARAM_CODE); } /** * 1. Handles the redirect/callback when user presses in sign in in browser or Etalio app. * 2. Initiates a request for a session and return the result of that request to the AuthenticationCallback. * @param intent The intent that might contain a Etalio sign in callback. * @param callback The AuthenticationCallback to handle resulting authentication. * @return boolean true if the intent contains an authorization callback that will be handled. false if there was no Etalio authentication callback within the intent. * @throws com.etalio.android.client.exception.EtalioAuthorizationCodeException */ public boolean handleSignInCallback(final Intent intent, final AuthenticationCallback callback) throws EtalioAuthorizationCodeException { if (isEtalioSignInCallback(intent)) { authorizeAndAcquireTokensFromCallback(intent, callback); return true; } return false; } /** * Call this method in authenticating activity's onActivityResult to handle Etalio single sign on results. * @param requestCode * @param resultCode * @param data */ public void onActivityResult(int requestCode, int resultCode, Intent data, AuthenticationCallback callback) { if (requestCode == REQUEST_CODE_SSO && resultCode == Activity.RESULT_OK) { String accessToken = data.getStringExtra(EtalioBase.EXTRA_ACCESS_TOKEN); String refreshToken = data.getStringExtra(EtalioBase.EXTRA_REFRESH_TOKEN); int expiresIn = data.getIntExtra(EtalioBase.EXTRA_EXPIRES_IN, 0); setEtalioToken(new EtalioToken(accessToken, refreshToken, expiresIn)); callback.onAuthenticationSuccess(); } else { callback.onAuthenticationFailure( new EtalioTokenException(EtalioTokenException.TokensErrorResponse.INVALID_RESPONSE)); } } /** * Requests an access token and a refresh token from Etalio */ protected void authorizeAndAcquireTokensFromCallback(final Intent intent, final AuthenticationCallback callback) throws EtalioAuthorizationCodeException { if (!isEtalioSignInCallback(intent)) { throw new IllegalArgumentException("The intent does not contain a valid authentication callback."); } //set the code for later final String code = getAuthorizationCodeFromUri(intent.getData()); //Reset intent data to not trigger this call again. if (intent != null) { intent.setData(null); } new AsyncTask<String, Void, Boolean>() { public EtalioTokenException error; @Override protected Boolean doInBackground(String... params) { //get and set the access/refresh tokens try { requestNewToken(params[0]); } catch (EtalioTokenException e) { this.error = e; return false; } catch (IOException e) { return false; } return true; } @Override protected void onPostExecute(Boolean result) { if (callback != null) { if (result) { callback.onAuthenticationSuccess(); } else { callback.onAuthenticationFailure(this.error); } } } }.execute(code); } /** * Returns the Etalio token object, or null if it's not stored yet. * @return EtalioToken containing access token, refresh token and expiration time. */ public EtalioToken getEtalioToken() { String serializedToken = getPersistentData(SupportedKey.ETALIO_TOKEN); if (Utils.isNullOrEmpty(serializedToken)) { return null; } return new EtalioToken(serializedToken); } /** * Sets the Etalio token object * @param t The etalio token to use for the session from now on. */ public void setEtalioToken(EtalioToken t) { setPersistentData(SupportedKey.ETALIO_TOKEN, t.toSerializedString()); } /** * * @return If there is a valid access token. If token expired it can be renewed with @see EtalioBase#requestRefreshToken(). * * NOTE: There is no guarantee that the session is active or can be renewed. * The access might be revoked from another client, this needs to be verified with an API request. */ public boolean hasEtalioSession() { EtalioToken token = getEtalioToken(); return token != null && token.isTokenExpired(); } /** * Do an authorized call against the API * * @param apiName the name of the api in the domainMap * @param method the http method, GET, POST, PUT or DELETE * @param outputBody an object to be sent as JSON if method is PUT, POST or DELETE. * @return the result as a String * @throws java.io.IOException if request fails */ protected <T> T apiCall(String apiName, HttpRequest.HttpMethod method, Object outputBody, Class<T> returnType, boolean signedCall) throws EtalioTokenException, EtalioHttpException, IOException { Map<String, String> headers = new HashMap<String, String>(); if (signedCall) { EtalioToken token = getEtalioToken(); if (token == null) { throw new EtalioTokenException(EtalioTokenException.TokensErrorResponse.INVALID_TOKEN); } if (token.willTokenSoonExpire()) { requestRefreshToken(); } headers = buildSignedApiCallHeaders(); } addEtalioUserAgent(headers); byte[] body; if (outputBody == null) { body = new byte[0]; } else { body = getHttpBodyConverter().toBody(outputBody, "UTF-8"); } HttpRequest request = new HttpRequest(method, getUrl(apiName, null), headers, body, CONTENT_TYPE_APPLICATION_JSON, body != null ? body.length : 0); Log.v(TAG, method + " " + getUrl(apiName, null) + " " + new String(body, "UTF8") + " "); HttpResponse<T> response; response = getHttpClient().executeRequest(request, getHttpBodyConverter(), returnType); if (response.getBody() != null) { Log.v(TAG, response.getStatus() + " : " + new String(getHttpBodyConverter().toBody(response.getBody(), "UTF-8"), "UTF-8")); } else { Log.v(TAG, "" + response.getStatus()); } return response.getBody(); } /** * Do an authorized call against the API * * @param apiName the name of the api in the domainMap * @param method the http method, GET, POST, PUT or DELETE * @param outputBody an object to be sent as JSON if method is PUT, POST or DELETE. * @return the result as a String * @throws java.io.IOException if request fails */ protected <T> T apiCall(String apiName, HttpRequest.HttpMethod method, Object outputBody, Class<T> returnType) throws EtalioTokenException, IOException, EtalioHttpException { return apiCall(apiName, method, outputBody, returnType, true); } /** * Returns the url for the given endpoint in the domainmap * * @param key the name of the endpoint in the domainmap * @param params optional map with key value parameters * @return the url to the given endpoint including request parameters */ protected String getUrl(String key, Map<String, String> params) { String url = domainMap.get(key); if (params != null && !params.isEmpty()) { boolean first = true; for (Map.Entry<String, String> param : params.entrySet()) { url += ((first) ? "?" : "&") + param.getKey() + "=" + param.getValue(); first = false; } } Log.d(TAG, url); return url; } /** * Build the URL for the first step in the oauth hand shake * * @param scope (Optional) If another scope than the default should be used * @return the Url as a String with Get parameters */ private Uri getSignInUrl(String scope) { Map<String, String> params = new HashMap<String, String>(); params.put(PARAM_CLIENT_ID, mClientId); params.put(PARAM_STATE, getState()); params.put(PARAM_REDIRECT_URI, mRedirectUri); params.put(PARAM_SDK, SDK_TAG + getVersion()); params.put(PARAM_RESPONSE_TYPE, "code"); if (!Utils.isNullOrEmpty(scope)) { params.put(PARAM_SCOPE, scope); } return Uri.parse(getUrl("oauth2", params)); } private String getVersion() { PackageInfo pInfo = null; try { pInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0); } catch (PackageManager.NameNotFoundException e) { return "unknown-version"; } return pInfo.versionName; } /** * Build the URL for exchanging the authorization code for an access token * * @param code The Code as a string * @return the POST body as a String. */ private byte[] buildTokenBody(String code) { if (Utils.isNullOrEmpty(code)) { throw new IllegalArgumentException("Code can not be empty"); } Map<String, String> params = new HashMap<String, String>(); params.put(PARAM_GRANT_TYPE, "authorization_code"); params.put(PARAM_REDIRECT_URI, mRedirectUri); params.put(PARAM_CODE, code); params.put(PARAM_CLIENT_ID, mClientId); params.put(PARAM_CLIENT_SECRET, mClientSecret); return generateFormEncodedRequestBody(params); } /** * Generates a form request body for application/x-www-form-urlencoded requests. * * Example: * client_id=7y7gp0495bt7acqbqdaw7y7gp0495bt7 * &client_secret=ckm6ssv30cwz1zg7xu2pckm6ssv30cwz1zg7xu2p * &redirect_uri=http%253A%252F%252Flocalhost%253A3000%252Fauth%252Fetalio%252Fcallback * &code=2260bbf6918ce3c1715366580cc568acf58afe45 * &grant_type=authorization_code * * @param params The key value pairs to generate the body for. * @return The request body as a byte array. */ protected byte[] generateFormEncodedRequestBody(Map<String, String> params) { String body = ""; Iterator<Map.Entry<String, String>> iterator = params.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, String> entry = iterator.next(); body += entry.getKey() + "=" + entry.getValue(); if (iterator.hasNext()) { body += "&"; } } try { return body.getBytes(UTF_8); } catch (UnsupportedEncodingException e) { Log.e(TAG, "Missing UTF-8 encoding", e); return null; } } /** * Builds the URL that exchanges the refresh token to for a new access token * * @return the Url as a String with Get parameters */ private byte[] buildRefreshTokenBody() { Map<String, String> params = new HashMap<String, String>(); params.put(PARAM_GRANT_TYPE, "refresh_token"); params.put(PARAM_REFRESH_TOKEN, getEtalioToken().getRefreshToken()); params.put(PARAM_CLIENT_ID, mClientId); params.put(PARAM_CLIENT_SECRET, mClientSecret); return generateFormEncodedRequestBody(params); } public Map<String, String> buildSignedApiCallHeaders() { Map<String, String> headers = new HashMap<String, String>(); headers.put("Authorization", " Bearer " + getEtalioToken().getAccessToken()); Log.d("Access Token", getEtalioToken().getAccessToken()); return headers; } protected void addEtalioUserAgent(Map<String, String> headers) { headers.put("User-Agent", getEtalioUserAgent()); } protected String getEtalioUserAgent() { Locale locale = Locale.getDefault(); Resources resources; resources = mContext.getApplicationContext().getResources(); return "Etalio/" + getVersion() + " (Linux; " + " Android " + Build.VERSION.RELEASE + "; " + locale.toString() + "; " + Build.MANUFACTURER + " " + Build.MODEL + " Build/" + Build.DISPLAY + "; " + " Density/" + (resources != null ? resources.getDisplayMetrics().density : "unknown") + ")"; } /** * Requests a new access token after authorization has already occurred. * @param authorizationCode The access code retrieved in the authentication request. */ void requestNewToken(String authorizationCode) throws EtalioTokenException, IOException { Log.d(TAG, "Requesting new token."); byte[] body = buildTokenBody(authorizationCode); String data = new String(body, "UTF-8"); Log.d(TAG, "Body: " + data); Map<String, String> headers = new HashMap<String, String>(); addEtalioUserAgent(headers); HttpRequest request = new HttpRequest(HttpRequest.HttpMethod.POST, getUrl("token", null), null, body, CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED, body.length); HttpResponse<TokenResponse> response = null; try { response = getHttpClient().executeRequest(request, new GsonHttpBodyConverter<TokenResponse>(), TokenResponse.class); } catch (EtalioHttpException e) { Log.e(TAG, "Error when fetching new token", e); parseEtalioTokenError(e); } updateEtalioToken(response); } /** * Requests a refresh of the access token to extend the session. */ public void requestRefreshToken() throws EtalioTokenException, IOException { Log.d(TAG, "Refreshing token."); if (getEtalioToken() == null) { throw new EtalioTokenException(EtalioTokenException.TokensErrorResponse.INVALID_TOKEN); } byte[] body = buildRefreshTokenBody(); Map<String, String> headers = new HashMap<String, String>(); addEtalioUserAgent(headers); HttpRequest request = new HttpRequest(HttpRequest.HttpMethod.POST, getUrl("token", null), headers, body, CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED, body.length); HttpResponse<TokenResponse> response = null; try { response = getHttpClient().executeRequest(request, new GsonHttpBodyConverter<TokenResponse>(), TokenResponse.class); } catch (EtalioHttpException e) { parseEtalioTokenError(e); } updateEtalioToken(response); } private void parseEtalioTokenError(EtalioHttpException e) throws EtalioTokenException { try { String message = e.getMessage(); JSONObject json = new JSONObject(message); throw new EtalioTokenException(json.getString("error")); } catch (JSONException ej) { throw new EtalioTokenException(EtalioTokenException.TokensErrorResponse.INVALID_REQUEST); } } /** * Validates the Etalio token from a HTTP response. * @param response The response to read token from. * @throws EtalioTokenException If the request is invalid or if the token contains an error message. */ protected void updateEtalioToken(HttpResponse<TokenResponse> response) throws EtalioTokenException { if (response == null) { throw new EtalioTokenException(EtalioTokenException.TokensErrorResponse.INVALID_RESPONSE); } TokenResponse token = response.getBody(); if (!Utils.isNullOrEmpty(token.getError())) { throw new EtalioTokenException(token.getError()); } setEtalioToken(new EtalioToken(token.getAccessToken(), token.getRefreshToken(), token.getExpiresIn())); } /** * Get the http client wrapper to be used for http request during authentication and API requests. Override this method in a subclass to use your own implementation. * @return The @see HttpClient implementation to use. */ protected HttpClient getHttpClient() { if (mHttpClient == null) { mHttpClient = new DefaultEtalioHttpClient(mContext); } return mHttpClient; } /** * Get the converter used converting from and to the http body in requests. Override this method in a subclass to use your own implementation. * @return The @see HttpBodyConverter implementation to use. */ protected HttpBodyConverter getHttpBodyConverter() { if (httpBodyConverter == null) { httpBodyConverter = new GsonHttpBodyConverter(); } return httpBodyConverter; } protected String multipartRequest(String urlTo, InputStream fileInputStream, String filefield, String filename) throws ParseException, IOException, EtalioHttpException { HttpURLConnection connection = null; DataOutputStream outputStream = null; InputStream inputStream = null; String twoHyphens = "--"; String boundary = "*****" + Long.toString(System.currentTimeMillis()) + "*****"; String lineEnd = "\r\n"; String result = ""; int bytesRead, bytesAvailable, bufferSize; byte[] buffer; int maxBufferSize = 1 * 1024 * 1024; try { URL url = new URL(urlTo); connection = (HttpURLConnection) url.openConnection(); connection.setDoInput(true); connection.setDoOutput(true); connection.setUseCaches(false); connection.setRequestMethod("POST"); connection.setRequestProperty("Connection", "Keep-Alive"); connection.setRequestProperty("Authorization", " Bearer " + getEtalioToken().getAccessToken()); connection.setRequestProperty("User-Agent", getEtalioUserAgent()); connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); outputStream = new DataOutputStream(connection.getOutputStream()); outputStream.writeBytes(twoHyphens + boundary + lineEnd); outputStream.writeBytes("Content-Disposition: form-data; name=\"" + filefield + "\"; filename=\"" + filename + "\"" + lineEnd); outputStream.writeBytes("Content-Type: " + MimeTypeUtil.getMimeType(filename) + lineEnd); outputStream.writeBytes("Content-Transfer-Encoding: binary" + lineEnd); outputStream.writeBytes(lineEnd); bytesAvailable = fileInputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); buffer = new byte[bufferSize]; bytesRead = fileInputStream.read(buffer, 0, bufferSize); while (bytesRead > 0) { outputStream.write(buffer, 0, bufferSize); bytesAvailable = fileInputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); bytesRead = fileInputStream.read(buffer, 0, bufferSize); } outputStream.writeBytes(lineEnd); outputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); inputStream = connection.getInputStream(); if (connection.getResponseCode() > 300) { result = convertStreamToString(connection.getErrorStream()); } else { result = this.convertStreamToString(inputStream); } fileInputStream.close(); inputStream.close(); outputStream.flush(); outputStream.close(); return result; } catch (Exception e) { throw new EtalioHttpException(connection.getResponseCode(), connection.getResponseMessage(), e.getMessage()); } } protected String convertStreamToString(InputStream is) { BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); String line = null; try { while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } return sb.toString(); } private void resetState() { clearPersistentData(SupportedKey.STATE); } protected String getState() { String state = getPersistentData(SupportedKey.STATE); if (Utils.isNullOrEmpty(state)) { state = setState(randomString()); } return state; } private String setState(String state) { setPersistentData(SupportedKey.STATE, state); return state; } private synchronized String randomString() { Random generator = new SecureRandom(); StringBuilder sb = new StringBuilder(); //Random length but at least half max length int randomLength = (48 / 2) + generator.nextInt(48 / 2); for (int i = 0; i < randomLength; i++) { sb.append(AB.charAt(generator.nextInt(AB.length()))); } return sb.toString(); } public abstract void setPersistentData(SupportedKey key, String val); public abstract String getPersistentData(SupportedKey key); public abstract void clearPersistentData(SupportedKey key); public abstract void clearAllPersistentData(); }