package de.tum.in.tumcampusapp.tumonline;
import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import com.google.common.base.Optional;
import com.google.common.net.UrlEscapers;
import org.simpleframework.xml.core.Persister;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import de.tum.in.tumcampusapp.R;
import de.tum.in.tumcampusapp.auxiliary.Const;
import de.tum.in.tumcampusapp.auxiliary.NetUtils;
import de.tum.in.tumcampusapp.auxiliary.Utils;
import de.tum.in.tumcampusapp.managers.CacheManager;
import de.tum.in.tumcampusapp.managers.TumManager;
import de.tum.in.tumcampusapp.models.tumo.TokenConfirmation;
/**
* This class will handle all action needed to communicate with the TUMOnline
* XML-RPC backend. ALl communications is based on the base-url which is
* attached by the Token and additional parameters.
*/
public final class TUMOnlineRequest<T> {
// server address
private static final String SERVICE_BASE_URL = "https://campus.tum.de/tumonline/wbservicesbasic.";
//private static final String SERVICE_BASE_URL = "https://campusquality.tum.de/QSYSTEM_TUM/wbservicesbasic.";
/**
* String possibly contained in response from server
*/
private static final String NO_FUNCTION_RIGHTS = "Keine Rechte für Funktion";
/**
* String possibly contained in response from server
*/
private static final String TOKEN_NOT_CONFIRMED = "Token ist nicht bestätigt oder ungültig!";
/**
* NetUtils instance for fetching
*/
private final NetUtils net;
private final CacheManager cacheManager;
private final TumManager tumManager;
/**
* Context
*/
private final Context mContext;
// force to fetch data and fill cache
private boolean fillCache;
// set to null, if not needed
private String accessToken;
/**
* asynchronous task for interactive fetch
*/
private AsyncTask<Void, Void, Optional<T>> backgroundTask;
/**
* method to call
*/
private TUMOnlineConst<T> method;
/**
* a list/map for the needed parameters
*/
private Map<String, String> parameters;
private String lastError = "";
private TUMOnlineRequest(Context context) {
mContext = context;
cacheManager = new CacheManager(context);
tumManager = new TumManager(context);
net = new NetUtils(context);
resetParameters();
}
public TUMOnlineRequest(TUMOnlineConst<T> method, Context context, boolean needsToken) {
this(context);
this.method = method;
if (needsToken) {
this.loadAccessTokenFromPreferences(context);
}
}
public TUMOnlineRequest(TUMOnlineConst<T> method, Context context) {
this(method, context, true);
this.fillCache = true;
}
public void cancelRequest(boolean mayInterruptIfRunning) {
// Cancel background task just if one has been established
if (backgroundTask != null) {
backgroundTask.cancel(mayInterruptIfRunning);
}
}
public static boolean checkTokenInactive(Context c) {
TUMOnlineRequest<TokenConfirmation> checkActiveToken = new TUMOnlineRequest<>(TUMOnlineConst.TOKEN_CONFIRMED, c, true);
Optional<TokenConfirmation> tc = checkActiveToken.fetch();
if (tc.isPresent()) { //Check that the token is actually active
if (tc.get().isConfirmed()) {
Utils.setSetting(c, Const.TUMO_DISABLED, false);
} else {
Utils.setSetting(c, Const.TUMO_DISABLED, true);//Nope its not, deactivate all requests to TUMOnline
return true;
}
}
//If we don't get anything, fail gracefully
return false;
}
/**
* Fetches the result of the HTTPRequest (which can be seen by using {@link #getRequestURL()})
*
* @return output will be a raw String
*/
public Optional<T> fetch() {
// set parameter on the TUMOnline request an fetch the results
String url = this.getRequestURL();
//If there were some requests that failed and we verified that the token is not active anymore, block all requests directly
if (!method.equals(TUMOnlineConst.TOKEN_CONFIRMED) && Utils.getSettingBool(mContext, Const.TUMO_DISABLED, false)) {
Utils.log("aborting fetch URL, as the token is not active any longer " + url);
return Optional.absent();
}
//Check for error lock
String lockedError = this.tumManager.checkLock(url);
if (lockedError != null) {
//If the token is not active, then fail hard and do not allow any further requests
if ("Token ist nicht bestätigt oder ungültig!".equals(lockedError)) {
TUMOnlineRequest.checkTokenInactive(mContext);
}
//Set the error and return
Utils.log("aborting fetch URL (" + lockedError + ") " + url);
lastError = lockedError;
return Optional.absent();
}
Utils.log("fetching URL " + url);
Optional<String> result;
try {
result = cacheManager.getFromCache(url);
if (NetUtils.isConnected(mContext) && (!result.isPresent() || fillCache)) {
result = net.downloadStringHttp(url);
}
} catch (IOException e) {
Utils.log(e, "FetchError");
lastError = e.getMessage();
result = Optional.absent();
}
T res = null;
if (result.isPresent()) {
try {
res = new Persister().read(method.getResponse(), result.get());
cacheManager.addToCache(url, result.get(), method.getValidity(), CacheManager.CACHE_TYP_DATA);
Utils.logv("added to cache " + url);
Utils.logv(result.get() + " " + res.toString());
//Release any lock present in the database
tumManager.releaseLock(url);
} catch (Exception e) {
//Serialisation failed - lock for a specific time, save the error message
lastError = tumManager.addLock(url, result.get());
}
}
return Optional.fromNullable(res);
}
/**
* this fetch method will fetch the data from the TUMOnline Request and will
* address the listeners onFetch if the fetch succeeded, else the
* onFetchError will be called
*
* @param context the current context (may provide the current activity)
* @param listener the listener, which takes the result
*/
public void fetchInteractive(final Context context, final TUMOnlineRequestFetchListener<T> listener) {
if (!loadAccessTokenFromPreferences(context)) {
listener.onFetchCancelled();
}
// fetch information in a background task and show progress dialog in
// meantime
backgroundTask = new AsyncTask<Void, Void, Optional<T>>() {
@Override
protected Optional<T> doInBackground(Void... params) {
// we are online, return fetch result
return fetch();
}
@Override
protected void onPostExecute(Optional<T> result) {
if (result.isPresent()) {
Utils.logv("Received result <" + result + '>');
} else {
Utils.log("No result available");
}
// Handles result
if (!NetUtils.isConnected(mContext)) {
if (result.isPresent()) {
Utils.showToast(mContext, R.string.no_internet_connection);
} else {
listener.onNoInternetError();
return;
}
}
//Check for common errors
if (!result.isPresent()) {
String error;
if (lastError.contains(TOKEN_NOT_CONFIRMED)) {
error = context.getString(R.string.dialog_access_token_invalid);
} else if (lastError.contains(NO_FUNCTION_RIGHTS)) {
error = context.getString(R.string.dialog_no_rights_function);
} else if (lastError.isEmpty()) {
error = context.getString(R.string.empty_result);
} else {
error = lastError;
}
listener.onFetchError(error);
return;
}
//Release any lock present in the database
tumManager.releaseLock(TUMOnlineRequest.this.getRequestURL());
// If there could not be found any problems return usual on Fetch method
listener.onFetch(result.get());
}
}.execute();
}
/**
* This will return the URL to the TUMOnlineRequest with regard to the set parameters
*
* @return a String URL
*/
public String getRequestURL() {
StringBuilder url = new StringBuilder(SERVICE_BASE_URL).append(method).append('?');
// Builds to be fetched URL based on the base-url and additional parameters
for (Entry<String, String> pairs : parameters.entrySet()) {
url.append(pairs.getKey()).append('=').append(pairs.getValue()).append('&');
}
return url.toString();
}
/**
* Check if TUMOnline access token can be retrieved from shared preferences.
*
* @param context The context
* @return true if access token is available; false otherwise
*/
private boolean loadAccessTokenFromPreferences(Context context) {
accessToken = PreferenceManager.getDefaultSharedPreferences(context).getString(Const.ACCESS_TOKEN, null);
// no access token set, or it is obviously wrong
if (accessToken == null || accessToken.length() < 1) {
return false;
}
// ok, access token seems valid (at first)
setParameter(Const.P_TOKEN, accessToken);
return true;
}
/**
* Reset parameters to an empty Map
*/
void resetParameters() {
parameters = new HashMap<>();
// set accessToken as parameter if available
if (accessToken != null) {
parameters.put(Const.P_TOKEN, accessToken);
}
}
/**
* Sets one parameter name to its given value
*
* @param name identifier of the parameter
* @param value value of the parameter
*/
public void setParameter(String name, String value) {
parameters.put(name, UrlEscapers.urlPathSegmentEscaper().escape(value));
}
public void setParameterEncoded(String name, String value) {
parameters.put(name, Uri.encode(value));
}
public void setForce(boolean force) {
fillCache = force;
}
public String getLastError() {
return this.lastError;
}
}