package com.circlegate.liban.ws; import android.os.SystemClock; import android.text.TextUtils; import com.circlegate.liban.task.TaskCommon.TaskParam; import com.circlegate.liban.task.TaskCommon.TaskResult; import com.circlegate.liban.task.TaskErrors.BaseError; import com.circlegate.liban.task.TaskErrors.ITaskError; import com.circlegate.liban.task.TaskErrors.TaskException; import com.circlegate.liban.task.TaskInterfaces.ITask; import com.circlegate.liban.task.TaskInterfaces.ITaskContext; import com.circlegate.liban.task.TaskInterfaces.ITaskParam; import com.circlegate.liban.task.TaskInterfaces.ITaskResult; import com.circlegate.liban.utils.EqualsUtils; import com.circlegate.liban.utils.LogUtils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class WsBase { public interface IWsParam extends ITaskParam { IWsResult createResultUncached(ITaskContext context, ITask task) throws TaskException; IWsResult createErrorResult(ITaskContext context, ITask task, ITaskError error); } public interface IWsResult extends ITaskResult { IWsParam getParam(); boolean isConnectionError(); } public static abstract class WsParam extends TaskParam implements IWsParam { protected static final int DEF_TIMEOUT_CONNECTION = 30000; protected static final int DEF_TIMEOUT_WRITE = 30000; protected static final int DEF_TIMEOUT_READ = 30000; private static OkHttpClient defaultClient; private String tag; // lazy loaded @Override public IWsResult createResultUncached(ITaskContext context, ITask task) throws TaskException { String tag = getTag(); long startTime = SystemClock.elapsedRealtime(); if (task.isCanceled()) { LogUtils.d(tag, "task canceled (1)"); return null; } Request request = createRequest(context, task).build(); Response response = null; try { int maxRetries = Math.max(1, getRetries(context, task)); for (int i = 0; i < maxRetries; i++) { LogUtils.d(tag, "retry " + i); if (task.isCanceled()) { LogUtils.d(tag, "task canceled (2)"); return null; } try { OkHttpClient client = getClient(context, task, i); response = client.newCall(request).execute(); int statusCode = response.code(); LogUtils.d(tag, "status code: " + statusCode); if (!isResponseCodeAcceptable(statusCode)) { return createErrorResult(context, task, BaseError.ERR_CONNECTION_ERROR_UNEXPECTED_RES); } break; } catch (IOException ex) { LogUtils.e(tag, "createResultUncached: exception while connecting - retry: " + i, ex); } if (i + 1 >= maxRetries) { LogUtils.d(tag, "too many retries (" + i + "), giving up"); return createErrorResult(context, task, BaseError.ERR_CONNECTION_ERROR_COMMUNICATION); } } try { IWsResult result = createResult(context, task, response); LogUtils.d(tag, "finished in time: " + (SystemClock.elapsedRealtime() - startTime)); return result; } catch (IOException ex) { LogUtils.e(tag, "error while reading response", ex); if (!TextUtils.isEmpty(ex.getMessage()) && ex.getMessage().contains(" ENOSPC ")) // pro zachceni vyjimky pri nedostatku mista v ulozisti: java.io.IOException: write failed: ENOSPC (No space left on device) return createErrorResult(context, task, BaseError.ERR_FILE_ERROR); else return createErrorResult(context, task, BaseError.ERR_CONNECTION_ERROR_COMMUNICATION); } } finally { try { if (response != null && response.body() != null) response.body().close(); } catch (Exception ex) { LogUtils.e(tag, "response.body().close() thrown exception"); } } } protected OkHttpClient getClient(ITaskContext context, ITask task, int retry) { synchronized (WsParam.class) { if (defaultClient == null) { defaultClient = WsUtils.createClientBuilder(DEF_TIMEOUT_CONNECTION, DEF_TIMEOUT_WRITE, DEF_TIMEOUT_READ).build(); } return defaultClient; } } protected boolean isResponseCodeAcceptable(int responseCode) { return responseCode == HttpURLConnection.HTTP_OK; } protected abstract int getRetries(ITaskContext context, ITask task); protected abstract Request.Builder createRequest(ITaskContext context, ITask task) throws TaskException; protected abstract IWsResult createResult(ITaskContext context, ITask task, Response acceptableResponse) throws TaskException, IOException; protected String getTag() { if (tag == null) tag = "WsParam-" + getClass().getSimpleName(); return tag; } } public static class WsResult<TWsParam extends IWsParam> extends TaskResult<TWsParam> implements IWsResult { public WsResult(TWsParam param, ITaskError error) { super(param, error); } @Override public final boolean isConnectionError() { return BaseError.isConnectionError(getError()); } } public static class WsFileParam extends WsParam { private final String uri; private final String fileDest; private final String serialExecutionKey; private final boolean canUseGzip; public WsFileParam(String uri, String fileDest, String serialExecutionKey, boolean canUseGzip) { this.uri = uri; this.fileDest = fileDest; this.serialExecutionKey = serialExecutionKey; this.canUseGzip = canUseGzip; } public String getUri() { return this.uri; } public String getFileDest() { return this.fileDest; } public String getSerialExecutionKey() { return this.serialExecutionKey; } public boolean getCanUseGzip() { return this.canUseGzip; } @Override public String getSerialExecutionKey(ITaskContext context) { return serialExecutionKey; } @Override public int hashCode() { int _hash = 17; _hash = _hash * 29 + EqualsUtils.hashCodeCheckNull(uri); _hash = _hash * 29 + EqualsUtils.hashCodeCheckNull(fileDest); _hash = _hash * 29 + EqualsUtils.hashCodeCheckNull(serialExecutionKey); _hash = _hash * 29 + (canUseGzip ? 1 : 0); return _hash; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof WsFileParam)) { return false; } WsFileParam lhs = (WsFileParam) o; return lhs != null && EqualsUtils.equalsCheckNull(uri, lhs.uri) && EqualsUtils.equalsCheckNull(fileDest, lhs.fileDest) && EqualsUtils.equalsCheckNull(serialExecutionKey, lhs.serialExecutionKey) && canUseGzip == lhs.canUseGzip; } @Override protected int getRetries(ITaskContext context, ITask task) { return 2; } @Override protected Request.Builder createRequest(ITaskContext context, ITask task) { Request.Builder ret = new Request.Builder().url(getUri()); WsUtils.setRequestCustomGzipResponseHandlingIfCan(ret, canUseGzip); return ret; } @Override protected IWsResult createResult(ITaskContext context, ITask task, Response acceptableResponse) throws IOException { FileOutputStream fileOutputStream = null; boolean downloaded = false; try { try { fileOutputStream = new FileOutputStream(getFileDest()); } catch (IOException ex) { LogUtils.e(getTag(), "IOException while opening FileOutputStream", ex); return createErrorResult(context, task, BaseError.ERR_FILE_ERROR); } downloaded = WsUtils.downloadResponseToStream(acceptableResponse, fileOutputStream, task, true, true, true, getTag()); if (downloaded) return new WsResult<WsParam>(this, BaseError.ERR_OK); else return null; } finally { try { if (fileOutputStream != null) fileOutputStream.close(); } catch (IOException ex) { LogUtils.e(getTag(), "IOException while closing FileOutputStream", ex); } if (!downloaded) { File f = new File(fileDest); f.delete(); } } } @Override public IWsResult createErrorResult(ITaskContext context, ITask task, ITaskError error) { return new WsResult<WsParam>(this, error); } } }