/* * Copyright 2013 Robotoworks Limited * 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.robotoworks.mechanoid.net; import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.LinkedHashMap; /** * <p> * Base for all generated Mechanoid Net service clients. * </p> */ public abstract class ServiceClient { private static final String DEFAULT_LOG_TAG = ServiceClient.class.getSimpleName(); protected static final String METHOD_GET = "GET"; protected static final String METHOD_PUT = "PUT"; protected static final String METHOD_POST = "POST"; protected static final String METHOD_DELETE = "DELETE"; protected static final String METHOD_PATCH = "PATCH"; private final LinkedHashMap<String, String> mHeaders = new LinkedHashMap<String, String>(); private final String mBaseUrl; private final boolean mDebug; private final JsonEntityReaderProvider mReaderProvider; private final JsonEntityWriterProvider mWriterProvider; private final int mConnectTimeout = 20000; private final int mReadTimeout = 20000; protected String getBaseUrl() { return mBaseUrl; } protected boolean isDebug() { return mDebug; } protected LinkedHashMap<String, String> getHeaders() { return mHeaders; } protected String getLogTag() { return DEFAULT_LOG_TAG; } /** * <p> * The reader provider for this client, if you want to override the returned reader provider, consider using * {@link #createReaderProvider()} instead. * </p> * * @return */ public JsonEntityReaderProvider getReaderProvider() { return mReaderProvider; } /** * <p> * The writer provider for this client, if you want to override the returned writer provider, consider using * {@link #createWriterProvider()} instead. * </p> * * @return */ public JsonEntityWriterProvider getWriterProvider() { return mWriterProvider; } /** * <p> * Add a request header to all requests performed by this client * </p> * * @param field * @param value */ public void setHeader(String field, String value) { getHeaders().put(field, value); } public ServiceClient(String baseUrl, boolean debug) { mBaseUrl = baseUrl; mDebug = debug; mReaderProvider = createReaderProvider(); mWriterProvider = createWriterProvider(); } /** * <p> * For advanced use only, override this to provide your own writer provider * </p> * * @return */ protected abstract JsonEntityWriterProvider createWriterProvider(); /** * <p> * For advanced use only, override this to provide your own reader provider * </p> * * @return */ protected abstract JsonEntityReaderProvider createReaderProvider(); protected <REQUEST extends ServiceRequest, RESULT extends ServiceResult> Response<RESULT> get( REQUEST request, Parser<RESULT> resultParser) throws ServiceException { try { URL url = createUrl(request); Response<RESULT> mockedResponse = createMockedResponse(url, request, resultParser); if (mockedResponse != null) { if (isDebug()) { Log.d(getLogTag(), METHOD_GET + " Mocked Response"); } return mockedResponse; } if (isDebug()) { Log.d(getLogTag(), METHOD_GET + " " + url.toString()); } HttpURLConnection conn = openConnection(url); applyRequestTimeouts(request, conn); conn.setRequestMethod(METHOD_GET); conn.setRequestProperty("Accept", "application/json, text/json"); applyRequestProperties(request, conn); if (isDebug()) { NetLogHelper.logProperties(getLogTag(), conn.getRequestProperties()); } conn.connect(); Response<RESULT> response = new HttpUrlConnectionResponse<RESULT>(conn, resultParser); if (isDebug()) { NetLogHelper.logProperties(getLogTag(), response.getHeaders()); Log.d(getLogTag(), response.readAsText()); } return response; } catch (Exception e) { throw new ServiceException(e); } } protected <REQUEST extends ServiceRequest, RESULT extends ServiceResult> Response<RESULT> delete( REQUEST request, Parser<RESULT> resultParser) throws ServiceException { try { URL url = createUrl(request); Response<RESULT> mockedResponse = createMockedResponse(url, request, resultParser); if (mockedResponse != null) { if (isDebug()) { Log.d(getLogTag(), METHOD_DELETE + " Mocked Response"); } return mockedResponse; } if (isDebug()) { Log.d(getLogTag(), METHOD_DELETE + " " + url.toString()); } HttpURLConnection conn = openConnection(url); applyRequestTimeouts(request, conn); conn.setRequestMethod(METHOD_DELETE); applyRequestProperties(request, conn); if (isDebug()) { NetLogHelper.logProperties(getLogTag(), conn.getRequestProperties()); } conn.connect(); Response<RESULT> response = new HttpUrlConnectionResponse<RESULT>(conn, resultParser); if (isDebug()) { NetLogHelper.logProperties(getLogTag(), response.getHeaders()); Log.d(getLogTag(), response.readAsText()); } return response; } catch (Exception e) { throw new ServiceException(e); } } protected <REQUEST extends ServiceRequest, RESULT extends ServiceResult> Response<RESULT> genericMethod(REQUEST request, Parser<RESULT> resultParser, String method) throws ServiceException { try { boolean xWwwFormUrlencoded = getHeaders().get("Content-Type") != null && getHeaders().get("Content-Type").startsWith("application/x-www-form-urlencoded"); URL url = createUrl(request, xWwwFormUrlencoded); Response<RESULT> mockedResponse = createMockedResponse(url, request, resultParser); if (mockedResponse != null) { if (isDebug()) { Log.d(getLogTag(), method + " Mocked Response"); } return mockedResponse; } if (isDebug()) { Log.d(getLogTag(), method + " " + url.toString()); } HttpURLConnection conn = openConnection(url); applyRequestTimeouts(request, conn); conn.setDoOutput(true); conn.setRequestMethod(method); if (!xWwwFormUrlencoded) { conn.setRequestProperty("Content-Type", "application/json, text/json"); } applyRequestProperties(request, conn); if (isDebug()) { NetLogHelper.logProperties(getLogTag(), conn.getRequestProperties()); } conn.connect(); if (request instanceof EntityEnclosedServiceRequest) { EntityEnclosedServiceRequest entityEnclosedRequest = (EntityEnclosedServiceRequest) request; if (isDebug()) { ByteArrayOutputStream debugOutStream = new ByteArrayOutputStream(); entityEnclosedRequest.writeBody(mWriterProvider, debugOutStream); Log.d(getLogTag(), new String(debugOutStream.toByteArray(), "UTF-8")); } entityEnclosedRequest.writeBody(mWriterProvider, conn.getOutputStream()); } // for x-www-form-urlencoded params send with OutputStream if (xWwwFormUrlencoded) { String payload = getParamPayload(request); if (isDebug()) { Log.d(getLogTag(), "x-www-form-urlencoded params:" + payload); } // send the POST out PrintWriter out = new PrintWriter(conn.getOutputStream()); out.print(payload); out.close(); } Response<RESULT> response = new HttpUrlConnectionResponse<RESULT>(conn, resultParser); if (isDebug()) { NetLogHelper.logProperties(getLogTag(), response.getHeaders()); Log.d(getLogTag(), response.readAsText()); } return response; } catch (Exception e) { throw new ServiceException(e); } } protected HttpURLConnection openConnection(URL url) throws IOException { return (HttpURLConnection) url.openConnection(); } protected <REQUEST extends ServiceRequest, RESULT extends ServiceResult> Response<RESULT> post(REQUEST request, Parser<RESULT> resultParser) throws ServiceException { return genericMethod(request, resultParser, METHOD_POST); } protected <REQUEST extends ServiceRequest, RESULT extends ServiceResult> Response<RESULT> put(REQUEST request, Parser<RESULT> resultParser) throws ServiceException { return genericMethod(request, resultParser, METHOD_PUT); } protected <REQUEST extends ServiceRequest, RESULT extends ServiceResult> Response<RESULT> patch(REQUEST request, Parser<RESULT> resultParser) throws ServiceException { return genericMethod(request, resultParser, METHOD_PATCH); } protected <REQUEST extends ServiceRequest> void applyRequestTimeouts( REQUEST request, HttpURLConnection conn) { if (request.getReadTimeout() > -1) { conn.setReadTimeout(request.getReadTimeout()); } else { conn.setReadTimeout(mReadTimeout); } if (request.getConnectTimeout() > -1) { conn.setConnectTimeout(request.getConnectTimeout()); } else { conn.setConnectTimeout(mConnectTimeout); } } /** * <p> * Sets request properties using this clients headers and then headers from the given request such that request properties * from the given request will override those set from this client. * </p> * * @param request The request to add headers from * @param conn The connection to add headers to */ protected <REQUEST extends ServiceRequest> void applyRequestProperties( REQUEST request, HttpURLConnection conn) { for (String key : getHeaders().keySet()) { conn.setRequestProperty(key, getHeaders().get(key)); } for (String key : request.getHeaderKeys()) { conn.setRequestProperty(key, request.getHeaderValue(key)); } } /** * <p> * get param payload from request, necessary in case of xWwwFormUrlencoded * </p> * * @param request * @return * @throws MalformedURLException */ protected <REQUEST extends ServiceRequest> String getParamPayload(REQUEST request) throws MalformedURLException { String urlStr = request.createUrl(getBaseUrl()); if (urlStr.indexOf("?") > 0) { urlStr = urlStr.substring(urlStr.indexOf("?") + 1); } return urlStr; } /** * <p> * Creates a url from the given request, in case of xWwwFormUrlencoded without params * </p> * * @param request * @return * @throws MalformedURLException */ protected <REQUEST extends ServiceRequest> URL createUrl(REQUEST request, boolean xWwwFormUrlencoded) throws MalformedURLException { String urlStr = request.createUrl(getBaseUrl()); if (xWwwFormUrlencoded && urlStr.indexOf("?") > 0) { urlStr = urlStr.substring(0, urlStr.indexOf("?")); } URL url = new URL(urlStr); return url; } protected <REQUEST extends ServiceRequest> URL createUrl(REQUEST request) throws MalformedURLException { return createUrl(request, false); } protected <REQUEST extends ServiceRequest, RESULT extends ServiceResult> Response<RESULT> createMockedResponse(URL url, REQUEST request, Parser<RESULT> resultParser) { return null; } }