/** * */ package com.github.lpezet.antiope2.retrofitted; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.Map; import java.util.concurrent.ExecutorService; import com.github.lpezet.antiope2.dao.http.HttpExecutionContext; import com.github.lpezet.antiope2.dao.http.IHttpNetworkIO; import com.github.lpezet.antiope2.dao.http.IHttpRequest; import com.github.lpezet.antiope2.dao.http.IHttpResponse; import com.github.lpezet.antiope2.metrics.BaseMetrics; import com.github.lpezet.antiope2.metrics.IMetrics; import com.github.lpezet.antiope2.metrics.IMetricsCollector; import com.github.lpezet.antiope2.retrofitted.converter.Converter; import com.github.lpezet.java.patterns.worker.AsyncWorker; import com.github.lpezet.java.patterns.worker.Callback; import com.github.lpezet.java.patterns.worker.IAsyncResult; /** * @author Luc Pezet */ public class RestHandler implements InvocationHandler { private final String mEndpoint; private final Converter mDefaultConverter; private final Map<Method, MethodInfo> mMethodDetailsCache; private final ErrorHandler mErrorHandler; private final IHttpNetworkIO<IHttpRequest, IHttpResponse> mIO; private final AsyncWorker<IHttpRequest, IHttpResponse> mAsyncIO; private final ExecutorService mExecutorService; private final IMetricsCollector mMetricsCollector; private RxSupport mRxSupport; RestHandler( String pEndpoint, IHttpNetworkIO<IHttpRequest, IHttpResponse> pIO, IMetricsCollector pMetricsCollector, ExecutorService pExecutorService, Converter pDefaultConverter, Map<Method, MethodInfo> pMethodDetailsCache, ErrorHandler pErrorHandler) { mEndpoint = pEndpoint; mMetricsCollector = pMetricsCollector; mDefaultConverter = pDefaultConverter; mMethodDetailsCache = pMethodDetailsCache; mErrorHandler = pErrorHandler; mIO = pIO; mAsyncIO = new AsyncWorker<IHttpRequest, IHttpResponse>(pExecutorService, pIO); mExecutorService = pExecutorService; } @SuppressWarnings("unchecked") // @Override public Object invoke(Object pProxy, Method pMethod, final Object[] pArgs) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (pMethod.getDeclaringClass() == Object.class) { return pMethod.invoke(this, pArgs); } MethodInfo methodInfo = getMethodInfo(mMethodDetailsCache, pMethod); IHttpRequest request = createRequest(methodInfo, pArgs); switch (methodInfo.getExecutionType()) { case SYNC: return invokeSync(methodInfo, request); case ASYNC: invokeAsync(methodInfo, request, (com.github.lpezet.antiope2.retrofitted.Callback) pArgs[pArgs.length - 1]); return null; // Async has void return type. case RX: return invokeRx(methodInfo, request); default: throw new IllegalStateException("Unknown response type: " + methodInfo.getExecutionType()); } } private Object invokeRx(final MethodInfo pMethodInfo, final IHttpRequest pRequest) { if (mRxSupport == null) { if (Platform.HAS_RX_JAVA) { mRxSupport = new RxSupport(); } else { throw new IllegalStateException("Found Observable return type but RxJava not present."); } } return mRxSupport.createRequestObservable(new RxSupport.Invoker() { @Override public void invoke(final Callback callback) { try { IAsyncResult<IHttpResponse> oAsyncResult = mAsyncIO.perform(pRequest); oAsyncResult.setCallback(new com.github.lpezet.java.patterns.worker.Callback<IHttpResponse>() { @Override public void onResult(IHttpResponse pResult) { try { Object result = createResult(pMethodInfo, pResult); callback.next(result); } catch (AntiopeError error) { callback.error(handleError(error)); } } @Override public void onException(Exception e) { if (IOException.class.isInstance(e)) callback.next(AntiopeError.networkFailure(pRequest, (IOException) e)); else callback.next(AntiopeError.unexpectedError(pRequest, e)); } }); } catch (Exception e) { e.printStackTrace(); } /* * Call call = client.newCall(request); * call.enqueue(new com.squareup.okhttp.Callback() { * @Override public void onFailure(Request request, IOException e) { * callback.next(RetrofitError.networkFailure(request.urlString(), e)); * } * @Override public void onResponse(Response response) { * try { * Object result = createResult(methodInfo, response); * callback.next(result); * } catch (RetrofitError error) { * callback.error(handleError(error)); * } * } * }); */ } }); } private void invokeAsync(final MethodInfo pMethodInfo, final IHttpRequest pRequest, final com.github.lpezet.antiope2.retrofitted.Callback pCallback) throws Exception { IAsyncResult<IHttpResponse> oAsyncResult = mAsyncIO.perform(pRequest); oAsyncResult.setCallback(new Callback<IHttpResponse>() { @Override public void onResult(IHttpResponse pResult) { try { Object result = createResult(pMethodInfo, pResult); callResponse(pCallback, result, pResult); } catch (AntiopeError error) { callFailure(pCallback, error); } } @Override public void onException(Exception e) { if (IOException.class.isInstance(e)) callFailure(pCallback, AntiopeError.networkFailure(pRequest, (IOException) e)); else callFailure(pCallback, AntiopeError.unexpectedError(pRequest, e)); } }); } private void callResponse(final com.github.lpezet.antiope2.retrofitted.Callback callback, final Object result, final IHttpResponse response) { callback.success(result, response); // TODO: Need it???? /* * callbackExecutor.execute(new Runnable() { * @Override public void run() { * callback.success(result, response); * } * }); */ } private void callFailure(final com.github.lpezet.antiope2.retrofitted.Callback callback, AntiopeError error) { Throwable throwable = handleError(error); if (throwable != error) { IHttpResponse response = error.getResponse(); if (response != null) { error = AntiopeError.unexpectedError(response, throwable); } else { error = AntiopeError.unexpectedError(error.getUrl(), throwable); } } final AntiopeError finalError = error; callback.failure(finalError); // TODO: Need it????? /* * callbackExecutor.execute(new Runnable() { * @Override public void run() { * callback.failure(finalError); * } * }); */ } private Object invokeSync(MethodInfo pMethodInfo, IHttpRequest pRequest) throws Throwable { try { IHttpResponse response = mIO.perform(pRequest); return createResult(pMethodInfo, response); } catch (IOException e) { throw handleError(AntiopeError.networkFailure(pRequest, e)); } catch (AntiopeError error) { throw handleError(error); } } /** * Create the object to return to the caller for a response. * * @throws AntiopeError * if any HTTP, network, or unexpected errors occurred. */ private Object createResult(MethodInfo methodInfo, IHttpResponse response) { try { return parseResult(methodInfo, response); } catch (AntiopeError error) { throw error; // Let our own errors pass through. } catch (IOException e) { e.printStackTrace(); throw AntiopeError.networkError(response, e); } catch (Throwable t) { throw AntiopeError.unexpectedError(response, t); } } /** * Parse the object to return to the caller from a response. * * @throws AntiopeError * on non-2xx response codes (kind = HTTP). * @throws IOException * on network problems reading the response data. * @throws RuntimeException * on malformed response data. */ private Object parseResult(MethodInfo methodInfo, IHttpResponse response) throws IOException { Type type = methodInfo.getResponseObjectType(); Converter oConverter = getConverter(methodInfo); int statusCode = response.getStatusCode(); if (statusCode < 200 || statusCode >= 300) { // response = Utils.readBodyToBytesIfNecessary(response); throw AntiopeError.httpError(response, oConverter, type); } if (type.equals(IHttpResponse.class)) { // TODO: Add support for Streaming /* * if (!methodInfo.isStreaming()) { * // Read the entire stream and replace with one backed by a byte[]. * response = Utils.readBodyToBytesIfNecessary(response); * } */ return response; } InputStream body = response.getContent(); if (statusCode == 204 || statusCode == 205) { // HTTP 204 No Content "...response MUST NOT include a message-body" // HTTP 205 Reset Content "...response MUST NOT include an entity" // String response.getHeaders().get("Content-Length"); if (response.getContentLength() > 0) { throw new IllegalStateException(statusCode + " response must not include body."); } return null; } // ExceptionCatchingRequestBody wrapped = new ExceptionCatchingRequestBody(body); try { return oConverter.deserialize(body, type); } catch (RuntimeException e) { // If the underlying input stream threw an exception, propagate that rather than // indicating that it was a conversion exception. // if (wrapped.threwException()) { // throw wrapped.getThrownException(); // } throw e; } } private Throwable handleError(AntiopeError error) { Throwable throwable = mErrorHandler.handleError(error); if (throwable == null) { return new IllegalStateException("Error handler returned null for wrapped exception.", error); } return throwable; } static MethodInfo getMethodInfo(Map<Method, MethodInfo> pCache, Method pMethod) { synchronized (pCache) { MethodInfo methodInfo = pCache.get(pMethod); if (methodInfo == null) { methodInfo = new MethodInfo(pMethod); pCache.put(pMethod, methodInfo); } return methodInfo; } } private IHttpRequest createRequest(MethodInfo pMethodInfo, Object[] pArgs) throws Exception { // Very Antiope2 specific IMetrics oMetrics = new BaseMetrics(); HttpExecutionContext oContext = new HttpExecutionContext(oMetrics, mMetricsCollector); RequestBuilder requestBuilder = new RequestBuilder(mEndpoint, pMethodInfo, getConverter(pMethodInfo), oContext); requestBuilder.setArguments(pArgs); return requestBuilder.build(); } private Converter getConverter(MethodInfo pMethodInfo) { return pMethodInfo.getConverter() == null ? mDefaultConverter : pMethodInfo.getConverter(); } }