com.vodafone360.people.service.transport.http.HttpConnectionThread.java Source code

Java tutorial

Introduction

Here is the source code for com.vodafone360.people.service.transport.http.HttpConnectionThread.java

Source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at
 * src/com/vodafone360/people/VODAFONE.LICENSE.txt or
 * http://github.com/360/360-Engine-for-Android
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each file and
 * include the License file at src/com/vodafone360/people/VODAFONE.LICENSE.txt.
 * If applicable, add the following below this CDDL HEADER, with the fields
 * enclosed by brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 * Copyright 2010 Vodafone Sales & Services Ltd.  All rights reserved.
 * Use is subject to license terms.
 */

package com.vodafone360.people.service.transport.http;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
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 android.util.Log;

import com.vodafone360.people.Settings;
import com.vodafone360.people.SettingsManager;
import com.vodafone360.people.engine.EngineManager.EngineId;
import com.vodafone360.people.engine.login.LoginEngine;
import com.vodafone360.people.service.io.QueueManager;
import com.vodafone360.people.service.io.Request;
import com.vodafone360.people.service.io.ResponseQueue;
import com.vodafone360.people.service.transport.DecoderThread;
import com.vodafone360.people.service.transport.DecoderThread.RawResponse;
import com.vodafone360.people.service.transport.IConnection;
import com.vodafone360.people.service.transport.http.authentication.AuthenticationManager;
import com.vodafone360.people.service.utils.hessian.HessianUtils;
import com.vodafone360.people.utils.LogUtils;

/**
 * HTTP connection thread handles issuing of RPG requests over HTTP to server.
 */
public class HttpConnectionThread implements Runnable, IConnection {
    private Thread mThread;

    private volatile boolean mIsConnectionRunning;

    private PollThread mPollThread;

    private DecoderThread mDecoder;

    private HttpClient mHttpClient;

    private int mRetryCount;

    private URI mRpgUrl;

    private boolean mIsPolling, mIsFirstTimePoll;

    //protected static final int E_HTTP_PROTOCOL = 2;

    private final Object mSendLock = new Object();

    private final Object mRunLock = new Object();

    public HttpConnectionThread(DecoderThread decoder) {
        super();
        mIsPolling = false;
        mIsFirstTimePoll = true;

        mDecoder = decoder;
    }

    /**
     * Starts the RPG connection as a thread and also launches the polling
     * thread.
     */
    public synchronized void startThread() {
        logI("RpgHttpConnectionThread.startThread()", "Starting Thread");

        setHttpClient();
        mIsConnectionRunning = true;

        mThread = new Thread(this);
        mThread.start();
    }

    /**
     * Sets HTTP settings.
     */
    public void setHttpClient() {
        int connectionTimeout = Settings.HTTP_CONNECTION_TIMEOUT;
        HttpParams myHttpParams = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(myHttpParams, connectionTimeout);
        HttpConnectionParams.setSoTimeout(myHttpParams, connectionTimeout);
        mHttpClient = new DefaultHttpClient(myHttpParams); // get http
    }

    /**
     * Stops the current thread.
     */
    public synchronized void stopThread() {
        mIsConnectionRunning = false;
        notifyOfItemInRequestQueue();// notify to make sure the thread exits if
                                     // it's waiting
        if (mPollThread != null) {
            mPollThread.stopConnection();
        }
        if (mHttpClient != null) {
            mHttpClient.getConnectionManager().shutdown();
            mHttpClient = null;
        }
    }

    /**
     * <p>
     * Sends out synchronous requests (for authentication) to the API and
     * asynchronous calls to the RPG as soon as there are requests on the
     * request queue.
     * </p>
     * <p>
     * If there are no requests the thread is set to wait().
     * </p>
     */
    public void run() {
        AuthenticationManager authMgr = null;

        authMgr = new AuthenticationManager(this);

        while (mIsConnectionRunning) { // loops through requests and sends them
                                       // out as needed
            authMgr.handleAuthRequests(); // TODO move this out. this should be
                                          // done by the LoginEngine directly

            if (null != mPollThread) {
                if (mIsFirstTimePoll) {
                    try {
                        mPollThread.invokePoll(PollThread.SHORT_POLLING_INTERVAL, PollThread.DEFAULT_BATCHSIZE,
                                PollThread.ACTIVE_MODE);
                    } catch (Exception e) {
                        // we do not do anything here as it is not a critical
                        // error
                        logI("RpgHttpConnection.run()", "Exception while doing 1st time poll!!");
                    } finally {
                        mIsFirstTimePoll = false;
                    }
                }

                List<Request> requests = QueueManager.getInstance().getApiRequests();
                if ((requests.size() > 0) && mPollThread.getHasCoverage()) {
                    if (null == mRpgUrl) { // TODO move this out of the loop
                                           // once we have a proper authMgr
                        try {
                            mRpgUrl = new URL(SettingsManager.getProperty(Settings.RPG_SERVER_KEY)
                                    + LoginEngine.getSession().userID).toURI();
                        } catch (Exception e) {
                            logE("RpgHttpConnectionThread.run()", "Could not set up URL", e);
                        }
                    }

                    mRetryCount = 0;
                    List<Integer> reqIds = new ArrayList<Integer>();
                    try {
                        byte[] reqData = prepareRPGRequests(requests, reqIds);

                        if (null != LoginEngine.getSession()) {
                            synchronized (mSendLock) {
                                if (mIsConnectionRunning) {
                                    if (Settings.ENABLED_TRANSPORT_TRACE) {
                                        HttpConnectionThread.logI("RpgTcpConnectionThread.run()",
                                                "\n \n \nSending a request: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
                                                        + HessianUtils.getInHessian(
                                                                new ByteArrayInputStream(reqData), true));
                                    }
                                    HttpResponse response = postHTTPRequest(reqData, mRpgUrl,
                                            Settings.HTTP_HEADER_CONTENT_TYPE);
                                    if (mIsConnectionRunning
                                            && SettingsManager.getBooleanProperty(Settings.ENABLE_RPG_KEY)
                                            && handleRpgResponse(response, reqIds)) {
                                        mPollThread.startRpgPolling();
                                    }
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        addErrorToResponseQueue(reqIds);// add error to the
                                                        // response queue
                    }
                }
            }

            try {
                synchronized (mRunLock) {
                    mRunLock.wait();
                }
            } catch (InterruptedException ie) {
                LogUtils.logE("HttpConnectionThread.run() Wait was interrupted", ie);
            }
        }
    }

    /**
     * Takes all requests objects and writes its serialized data to a byte array
     * for further posting to the RPG.
     * 
     * @param requests A list of requests to serialize.
     * @param reqIds
     * @return The serialized requests with RPG headers. Returns NULL id list of requests is NULL.
     */
    private byte[] prepareRPGRequests(List<Request> requests, List<Integer> reqIds) {
        if (null == requests) {
            return null;
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            for (Request request : requests) {
                request.writeToOutputStream(baos, true);
                request.setActive(true);
                reqIds.add(request.getRequestId());
            }
            baos.flush();
        } catch (IOException ioe) {
            LogUtils.logE("HttpConnectionThread.prepareRPGRequests() Failed writing to BAOS", ioe);
        } finally {
            try {
                baos.close();
            } catch (IOException ioe) {
                LogUtils.logE("HttpConnectionThread.prepareRPGRequests() Failed closing BAOS", ioe);
            }
        }
        byte[] reqData = baos.toByteArray();

        return reqData;
    }

    /**
     * Posts the serialized data to the RPG and synchronously grabs the
     * response.
     * 
     * @param postData The post data to send to the RPG.
     * @param uri The URL to send the request to.
     * @param contentType The content type to send as, usually
     *            "application/binary)
     * @return The response data as a byte array.
     * @throws An exception if the request went wrong after HTTP_MAX_RETRY_COUNT
     *             retries.
     */
    public HttpResponse postHTTPRequest(byte[] postData, URI uri, String contentType) throws Exception {

        HttpResponse response = null;

        if (null == postData) {
            return response;
        }
        mRetryCount++;
        if (uri != null) {
            logI("RpgHttpConnectionThread.postHTTPRequest()",
                    "HTTP Requesting URI " + uri.toString() + " " + contentType);
            HttpPost httpPost = new HttpPost(uri);
            httpPost.addHeader("Content-Type", contentType);
            httpPost.addHeader("User-Agent", "PeopleRPGClient/1.0");
            httpPost.addHeader("Cache-Control", "no-cache");
            httpPost.setEntity(new ByteArrayEntity(postData));
            try {
                response = mHttpClient.execute(httpPost);
            } catch (Exception e) {
                e.printStackTrace();
                // repeat the request N times
                if (mRetryCount < Settings.HTTP_MAX_RETRY_COUNT) {
                    return postHTTPRequest(postData, uri, contentType);
                } else {
                    throw new Exception("Could not post request " + e);
                }
            }
        }
        return response;
    }

    /**
     * Checks if the response to an RPG request was fired off correctly.
     * Basically this method only checks whether the response is returned under
     * a HTTP 200 status.
     * 
     * @param response The response to check for.
     * @param reqIds The request IDs for the response.
     * @return True if the RPG response returned correctly.
     * @throws Exception Thrown if the response was null or the status line could
     *             not be fetched.
     */
    private boolean handleRpgResponse(HttpResponse response, List<Integer> reqIds) throws Exception {
        boolean ret = false;
        if (null != response) {
            if (null != response.getStatusLine()) {
                int respCode = response.getStatusLine().getStatusCode();
                logI("RpgHttpConnectionThread.handleRpgResponse()", "HTTP BINARY Got response status: " + respCode);
                switch (respCode) {
                case HttpStatus.SC_OK:
                case HttpStatus.SC_CONTINUE:
                case HttpStatus.SC_CREATED:
                case HttpStatus.SC_ACCEPTED:
                case HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION:
                case HttpStatus.SC_NO_CONTENT:
                case HttpStatus.SC_RESET_CONTENT:
                case HttpStatus.SC_PARTIAL_CONTENT:
                case HttpStatus.SC_MULTI_STATUS:
                    ret = true;
                    break;
                default:
                    addErrorToResponseQueue(reqIds);
                }
                finishResponse(response);
            } else {
                throw new Exception("Status line of response was null.");
            }
        } else {
            throw new Exception("Response was null.");
        }
        return ret;
    }

    /**
     * Finishes reading the response in order to unblock the current connection.
     * 
     * @param response The response to finish reading on.
     */
    private void finishResponse(HttpResponse response) {
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            try {
                // this is important! otherwise the connection remains
                // unusable!!
                entity.consumeContent();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Handles the synchronous responses for the authentication calls which go
     * against the API directly by adding it to the queue and checking if the
     * response code was a HTTP 200. TODO: this should be refactored into a
     * AuthenticationManager class.
     * 
     * @param response The response to add to the decoder.
     * @param reqIds The request IDs the response is to be decoded for.
     * @throws Exception Thrown if the status line could not be read or the
     *             response is null.
     */
    public void handleApiResponse(HttpResponse response, List<Integer> reqIds) throws Exception {
        byte[] ret = null;
        if (null != response) {
            if (null != response.getStatusLine()) {
                int respCode = response.getStatusLine().getStatusCode();
                logI("RpgHttpConnectionThread.handleApiResponse()", "HTTP Got response status: " + respCode);
                switch (respCode) {
                case HttpStatus.SC_OK:
                case HttpStatus.SC_CONTINUE:
                case HttpStatus.SC_CREATED:
                case HttpStatus.SC_ACCEPTED:
                case HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION:
                case HttpStatus.SC_NO_CONTENT:
                case HttpStatus.SC_RESET_CONTENT:
                case HttpStatus.SC_PARTIAL_CONTENT:
                case HttpStatus.SC_MULTI_STATUS:
                    HttpEntity entity = response.getEntity();
                    if (null != entity) {
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();

                        InputStream is = entity.getContent();
                        if (null != is) {
                            int nextByte = 0;
                            while ((nextByte = is.read()) != -1) {
                                baos.write(nextByte);
                            }
                            baos.flush();
                            ret = baos.toByteArray();
                            baos.close();
                            baos = null;
                        }
                        entity.consumeContent();
                    }

                    if (Settings.ENABLED_TRANSPORT_TRACE) {
                        int length = 0;
                        if (ret != null) {
                            length = ret.length;
                        }
                        HttpConnectionThread.logI("ResponseReader.handleApiResponse()",
                                "\n \n \n" + "Response with length " + length + " bytes received "
                                        + "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + (length == 0 ? ""
                                                : HessianUtils.getInHessian(new ByteArrayInputStream(ret), false)));

                    }
                    addToDecoder(ret, reqIds);
                    break;
                default:
                    addErrorToResponseQueue(reqIds);
                }
            } else {
                throw new Exception("Status line of response was null.");
            }
        } else {
            throw new Exception("Response was null.");
        }
    }

    /**
     * Adds a response to the response decoder.
     * 
     * @param input The data of the response.
     * @param reqIds The request IDs that a response was received for.
     */
    private void addToDecoder(byte[] input, List<Integer> reqIds) {
        if (input != null && mDecoder != null) {
            int reqId = reqIds.size() > 0 ? reqIds.get(0) : 0;
            mDecoder.addToDecode(new RawResponse(reqId, input, false, false));
            logI("RpgHttpConnectionThread.handleApiResponse()",
                    "Added response(s) to decoder: " + reqIds.toString());
        }
    }

    /**
     * Adds errors to the response queue whenever there is an HTTP error on the
     * backend.
     * 
     * @param reqIds The request IDs the error happened for.
     */
    public void addErrorToResponseQueue(List<Integer> reqIds) {
        EngineId source = null;
        QueueManager requestQueue = QueueManager.getInstance();
        ResponseQueue responseQueue = ResponseQueue.getInstance();
        for (Integer reqId : reqIds) {
            // attempt to get type from request
            Request req = requestQueue.getRequest(reqId);
            if (req != null)
                source = req.mEngineId;
            responseQueue.addToResponseQueue(reqId, null, source);
        }
    }

    /**
     * Kicks the request queue as soon as there are more requests on the queue.
     */
    @Override
    public void notifyOfItemInRequestQueue() {
        HttpConnectionThread.logI("HttpConnectionThread.notifyOfItemInRequestQueue()", "NEW REQUEST AVAILABLE!");
        synchronized (mRunLock) {
            mRunLock.notify();
        }
    }

    /**
     * Called whenever the device regains network coverage. It will kick the
     * request queue to see if there are more requests to send.
     */
    @Override
    public void notifyOfRegainedNetworkCoverage() {
        synchronized (mRunLock) {
            mRunLock.notify();
        }
    }

    /**
     * This method is called when log-in is detected. Polling should not
     * normally start before we are logged in as the backend needs a correct
     * user id.
     */
    private synchronized void startPollThread() {
        if (null == mPollThread) {
            mPollThread = new PollThread(this);
        }

        if (mIsConnectionRunning) {
            mIsPolling = true;
            mPollThread.startConnection(mDecoder);
        }
    }

    /**
     * Stops the polling thread. This method is implemented from the
     * IQueueListener-interface. It is called when log-out of the account is
     * detected but a connection is still ongoing. In this case polling needs to
     * stop.
     */
    private synchronized void stopPollThread() {
        if (mPollThread != null) {
            mIsPolling = false;
            mPollThread.stopConnection();
        }
        // clear all requests in RequestQueue
        QueueManager.getInstance().clearActiveRequests(false);
    }

    @Override
    public void onLoginStateChanged(boolean isLoggedIn) {
        if (!isLoggedIn && mIsConnectionRunning) {
            logI("RpgHttpConnectionThread.onLoginStateChanged()", "Stopping to poll.");
            stopPollThread();
            mIsPolling = false;
        } else if (!mIsConnectionRunning) {
            startThread();
        } else if (isLoggedIn && !mIsPolling) { // if connected but not Polling
            if (mIsConnectionRunning) {
                logI("RpgHttpConnectionThread.onLoginStateChanged()", "Starting Poll");
                startPollThread();
                mIsPolling = true;
            }
        }
    }

    @Override
    public boolean getIsConnected() {
        return mIsConnectionRunning;
    }

    public static void logE(String tag, String message, Throwable error) {
        if (Settings.ENABLED_TRANSPORT_TRACE) {
            Thread t = Thread.currentThread();
            if (null != error) {
                Log.e("(PROTOCOL)", "\n \n \n ################################################## \n" + tag + "["
                        + t.getName() + "]" + " : " + message, error);
            } else {
                Log.e("(PROTOCOL)", "\n \n \n ################################################## \n" + tag + "["
                        + t.getName() + "]" + " : " + message);
            }
        }

        if (null != error) {
            LogUtils.logE(message + " : " + error.toString());
        } else {
            LogUtils.logE(message + " (Note: Settings.ENABLED_TRANSPORT_TRACE might give you more details!)");
        }
    }

    public static void logW(String tag, String message) {
        if (Settings.ENABLED_TRANSPORT_TRACE) {
            Thread t = Thread.currentThread();
            Log.w("(PROTOCOL)", tag + "[" + t.getName() + "] ################### " + " : " + message);
        }
    }

    public static void logI(String tag, String message) {
        if (Settings.ENABLED_TRANSPORT_TRACE) {
            Thread t = Thread.currentThread();
            Log.i("(PROTOCOL)", tag + "[" + t.getName() + "]" + " : " + message);
        }
    }

    public static void logD(String tag, String message) {
        if (Settings.ENABLED_TRANSPORT_TRACE) {
            Thread t = Thread.currentThread();
            Log.d("(PROTOCOL)", tag + "[" + t.getName() + "]" + " : " + message);
        }
    }

    public static void logV(String tag, String message) {
        if (Settings.ENABLED_TRANSPORT_TRACE) {
            Thread t = Thread.currentThread();
            Log.v("(PROTOCOL)", tag + "[" + t.getName() + "]" + " : " + message);
        }
    }

    @Override
    public boolean getIsRpgConnectionActive() {
        return mIsPolling;
    }

    @Override
    public void notifyOfUiActivity() {
        // TODO Auto-generated method stub

    }
}