/*
* This software is released under the GNU Lesser General Public License v3.
* For more information see http://www.gnu.org/licenses/lgpl.html
*
* Copyright (c) 2011, Peter Knego & Matjaz Tercelj
* All rights reserved.
*/
package com.leanengine;
import android.os.AsyncTask;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
class RestService {
private static RestService INSTANCE = new RestService();
private volatile HttpClient client;
private Configuration configuration = new Configuration(); // default
public static RestService getInstance() {
return INSTANCE;
}
private HttpClient getClient() {
if (client == null) {
synchronized (this) {
if (client == null) {
if (configuration != null) {
final HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, configuration.getConnectionTimeout());
HttpProtocolParams.setVersion(params, configuration.getHttpVersion());
HttpProtocolParams.setContentCharset(params, configuration.getContentCharset());
final SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", configuration.getPlainFactory(), configuration.getPort()));
schemeRegistry.register(new Scheme("https", configuration.getSslFactory(), configuration.getSslPort()));
final ClientConnectionManager ccm = configuration.createClientConnectionManager(params, schemeRegistry);
client = configuration.createClient(ccm, params);
} else {
client = new DefaultHttpClient();
}
}
}
}
return client;
}
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
/**
* Dispose http client / its connection manager once done.
*/
public void dispose() {
final HttpClient temp = client;
client = null;
if (temp != null) {
final ClientConnectionManager manager = temp.getConnectionManager();
if (manager != null)
manager.shutdown();
}
}
private JSONObject doGet(String uri) throws IOException {
HttpGet httpget = new HttpGet(uri);
httpget.addHeader("Accept", "application/json");
httpget.addHeader("Accept-Charset", "UTF-8");
return getClient().execute(httpget, new RestResponseHandler());
}
private JSONObject doPost(String uri, JSONObject json) throws IOException {
HttpPost httpPost = new HttpPost(uri);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Accept-Charset", "UTF-8");
httpPost.addHeader("Content-Type", "application/json");
httpPost.setEntity(new StringEntity(json.toString(), "UTF-8"));
return getClient().execute(httpPost, new RestResponseHandler());
}
private void doDelete(String uri) throws IOException {
HttpDelete httpget = new HttpDelete(uri);
httpget.addHeader("Content-Type", "application/json");
getClient().execute(httpget, new DeleteResponseHandler());
}
protected LeanEntity getPrivateEntity(final String kind, final Long id) throws LeanException, IllegalArgumentException {
if (!LeanAccount.isUserLoggedIn())
throw new LeanException(LeanError.Type.NotAuthorizedError);
String url;
if (kind != null && id != null) {
url = LeanEngine.getHostURI() +
"/rest/v1/entity/" + kind + "/" + id + "?lean_token=" +
LeanEngine.getAuthToken();
} else {
throw new IllegalArgumentException("Parameters 'kind' and 'id' must not be null.");
}
try {
JSONObject jsonObject = doGet(url);
return JsonDecode.entityFromJson(jsonObject);
} catch (IOException e) {
throw new LeanException(LeanError.Type.NetworkError);
}
}
protected void getPrivateEntityAsync(final String kind, final long id,
final NetworkCallback<LeanEntity> networkCallback) {
final RestAsyncTask<LeanEntity[]> aTask = new RestAsyncTask<LeanEntity[]>() {
// executes on background thread
@Override
protected LeanEntity[] doInBackground(Void... lists) {
try {
return new LeanEntity[]{getPrivateEntity(kind, id)};
} catch (LeanException e) {
error = e.getError();
return null;
}
}
// executes on UI thread
@Override
protected void onPostExecute(LeanEntity[] leanEntities) {
if (error != null) {
networkCallback.onFailure(error);
return;
}
networkCallback.onResult(leanEntities);
}
};
aTask.execute((Void) null);
}
public void deletePrivateEntity(String kind, Long id) throws LeanException {
if (!LeanAccount.isUserLoggedIn())
throw new LeanException(LeanError.Type.NotAuthorizedError);
String url;
if (kind != null && id != null) {
url = LeanEngine.getHostURI() +
"/rest/v1/entity/" + kind + "/" + id + "?lean_token=" +
LeanEngine.getAuthToken();
} else {
throw new IllegalArgumentException("Parameters 'kind' and 'id' must not be null.");
}
try {
doDelete(url);
} catch (IOException e) {
throw new LeanException(LeanError.Type.NetworkError);
}
}
public void deletePrivateEntityAsync(final String kind, final long id, final NetworkCallback<Void> networkCallback)
throws LeanException {
final RestAsyncTask<Void> aTask = new RestAsyncTask<Void>() {
// executes on background thread
@Override
protected Void doInBackground(Void... lists) {
try {
deletePrivateEntity(kind, id);
return null;
} catch (LeanException e) {
error = e.getError();
return null;
}
}
// executes on UI thread
@Override
protected void onPostExecute(Void anything) {
if (error != null) {
networkCallback.onFailure(error);
return;
}
networkCallback.onResult((Void) null);
}
};
aTask.execute((Void) null);
}
protected LeanEntity[] getPrivateEntities(final String kind) throws LeanException {
if (!LeanAccount.isUserLoggedIn())
throw new LeanException(LeanError.Type.NotAuthorizedError);
String url;
if (kind != null) {
url = LeanEngine.getHostURI() +
"/rest/v1/entity/" + kind + "?lean_token=" +
LeanEngine.getAuthToken();
} else {
url = LeanEngine.getHostURI() +
"/rest/v1/entity?lean_token=" +
LeanEngine.getAuthToken();
}
try {
JSONObject jsonObject = doGet(url);
return JsonDecode.entityListFromJson(jsonObject);
} catch (IOException e) {
throw new LeanException(LeanError.Type.NetworkError);
}
}
protected void getPrivateEntitiesAsync(final String kind, final NetworkCallback<LeanEntity> networkCallback) {
final RestAsyncTask<LeanEntity[]> aTask = new RestAsyncTask<LeanEntity[]>() {
// executes on background thread
@Override
protected LeanEntity[] doInBackground(Void... lists) {
try {
return getPrivateEntities(kind);
} catch (LeanException e) {
error = e.getError();
return null;
}
}
// executes on UI thread
@Override
protected void onPostExecute(LeanEntity[] leanEntities) {
if (error != null) {
networkCallback.onFailure(error);
return;
}
networkCallback.onResult(leanEntities);
}
};
aTask.execute((Void) null);
}
protected long putPrivateEntity(final LeanEntity entity) throws LeanException {
if (!LeanAccount.isUserLoggedIn())
throw new LeanException(LeanError.Type.NotAuthorizedError);
//todo externalize URLs (and token insertion)
String url = LeanEngine.getHostURI() +
"/rest/v1/entity/" + entity.kind + "?lean_token=" +
LeanEngine.getAuthToken();
try {
JSONObject param = JsonEncode.entityToJson(entity);
JSONObject jsonObject = doPost(url, param);
return idFromJson(jsonObject);
} catch (IOException e) {
throw new LeanException(LeanError.Type.NetworkError);
}
}
protected void putPrivateEntityAsync(final LeanEntity entity, final NetworkCallback<Long> networkCallback) {
RestAsyncTask<Long> aTask = new RestAsyncTask<Long>() {
// executes on background thread
@Override
protected Long doInBackground(Void... lists) {
try {
return putPrivateEntity(entity);
} catch (LeanException e) {
error = e.getError();
return -1l;
}
}
// executes on UI thread
@Override
protected void onPostExecute(Long entityID) {
if (error != null) {
networkCallback.onFailure(error);
return;
}
networkCallback.onResult(entityID);
}
};
aTask.execute((Void) null);
}
protected LeanEntity[] queryPrivate(final LeanQuery query) throws LeanException {
if (!LeanAccount.isUserLoggedIn())
throw new LeanException(LeanError.Type.NotAuthorizedError);
String url = LeanEngine.getHostURI() +
"/rest/v1/query?lean_token=" +
LeanEngine.getAuthToken();
try {
JSONObject queryJson = JsonEncode.queryToJson(query);
JSONObject jsonObject = doPost(url, queryJson);
// update the Query's cursor - used in fetching next results
String cursor = jsonObject.optString("cursor");
if (cursor.length() != 0)
query.setCursor(cursor);
return JsonDecode.entityListFromJson(jsonObject);
} catch (IOException e) {
throw new LeanException(LeanError.Type.NetworkError);
}
}
protected void queryPrivateAsync(final LeanQuery query, final NetworkCallback<LeanEntity> networkCallback) {
final RestAsyncTask<LeanEntity[]> aTask = new RestAsyncTask<LeanEntity[]>() {
// executes on background thread
@Override
protected LeanEntity[] doInBackground(Void... lists) {
try {
return queryPrivate(query);
} catch (LeanException e) {
error = e.getError();
return null;
}
}
// executes on UI thread
@Override
protected void onPostExecute(LeanEntity[] leanEntities) {
if (error != null) {
networkCallback.onFailure(error);
return;
}
networkCallback.onResult(leanEntities);
}
};
aTask.execute((Void) null);
}
protected Boolean logout() throws LeanException {
if (!LeanAccount.isUserLoggedIn())
throw new LeanException(LeanError.Type.NotAuthorizedError);
String url = LeanEngine.getHostURI() +
"/rest/v1/public/logout?lean_token=" +
LeanEngine.getAuthToken();
try {
JSONObject jsonObject = doGet(url);
boolean result = resultFromJson(jsonObject);
// after the request is made we must also clear local data
LeanEngine.resetAuthToken();
LeanEngine.clearCookies();
return result;
} catch (IOException e) {
throw new LeanException(LeanError.Type.NetworkError);
}
}
public void logoutAsync(final NetworkCallback<Boolean> callback) {
final RestAsyncTask<Boolean[]> aTask = new RestAsyncTask<Boolean[]>() {
// executes on background thread
@Override
protected Boolean[] doInBackground(Void... lists) {
try {
return new Boolean[]{logout()};
} catch (LeanException e) {
error = e.getError();
return null;
}
}
// executes on UI thread
@Override
protected void onPostExecute(Boolean[] result) {
if (error != null) {
callback.onFailure(error);
return;
}
callback.onResult(result);
}
};
aTask.execute((Void) null);
}
public LeanAccount getCurrentAccountData() throws LeanException {
if (!LeanAccount.isUserLoggedIn())
throw new LeanException(LeanError.Type.NotAuthorizedError);
String url = LeanEngine.getHostURI() +
"/rest/v1/public/account?lean_token=" +
LeanEngine.getAuthToken();
try {
JSONObject jsonObject = doGet(url);
return JsonDecode.accountFromJson(jsonObject);
} catch (IOException e) {
throw new LeanException(LeanError.Type.NetworkError);
}
}
public void getCurrentAccountDataAsync(final NetworkCallback<LeanAccount> networkCallback) throws LeanException {
final RestAsyncTask<LeanAccount[]> aTask = new RestAsyncTask<LeanAccount[]>() {
// executes on background thread
@Override
protected LeanAccount[] doInBackground(Void... lists) {
try {
return new LeanAccount[]{getCurrentAccountData()};
} catch (LeanException e) {
error = e.getError();
return null;
}
}
// executes on UI thread
@Override
protected void onPostExecute(LeanAccount[] leanAccounts) {
if (error != null) {
networkCallback.onFailure(error);
return;
}
networkCallback.onResult(leanAccounts);
}
};
aTask.execute((Void) null);
}
protected static abstract class RestAsyncTask<Result> extends AsyncTask<Void, Void, Result> {
protected LeanError error;
}
protected static class RestResponseHandler implements ResponseHandler<JSONObject> {
@Override
public JSONObject handleResponse(HttpResponse response) throws LeanException {
StatusLine statusLine = response.getStatusLine();
HttpEntity entity = response.getEntity();
String result;
try {
result = entity != null ? EntityUtils.toString(entity, "UTF-8") : null;
} catch (IOException e) {
throw new LeanException(LeanError.Type.NetworkError);
}
if (statusLine.getStatusCode() >= 300) {
throw new LeanException(LeanError.fromJSON(result));
}
if (result != null) {
try {
return new JSONObject(result);
} catch (JSONException e) {
throw new LeanException(LeanError.Type.ServerError, "Malformed JSON reply: " + e.getMessage());
}
} else {
throw new LeanException(LeanError.Type.ServerError, "Malformed JSON reply.");
}
}
}
protected static class DeleteResponseHandler implements ResponseHandler<Void> {
@Override
public Void handleResponse(HttpResponse response) throws LeanException {
StatusLine statusLine = response.getStatusLine();
if (statusLine.getStatusCode() >= 300) {
throw new LeanException(LeanError.Type.ServerError);
}
return null;
}
}
private static Long idFromJson(JSONObject json) throws LeanException {
try {
return json.getLong("id");
} catch (JSONException e) {
throw new LeanException(LeanError.Type.ServerError, "Malformed JSON reply: missing field 'id'.");
}
}
private static boolean resultFromJson(JSONObject json) throws LeanException {
try {
return json.getBoolean("result");
} catch (JSONException e) {
throw new LeanException(LeanError.Type.ServerError, "Malformed JSON reply: missing field 'result'.");
}
}
}