/* * 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.tcp; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; import com.vodafone360.people.Settings; import com.vodafone360.people.service.io.rpg.RpgHeader; import com.vodafone360.people.service.transport.DecoderThread; import com.vodafone360.people.service.transport.http.HttpConnectionThread; import com.vodafone360.people.service.utils.hessian.HessianUtils; import com.vodafone360.people.utils.LogUtils; /** * Reads responses from a TCP sockets and adds them to the response decoder. * Errors are called and passed back to the RpgTcpConnectionThread. * * @author Rudy Norff (rudy.norff@vodafone.com) */ public class ResponseReaderThread implements Runnable { /** * Sleep time (in milliseconds) of the thread in between reads. */ private static final long THREAD_SLEEP_TIME = 300; // ms /** * The ResponseReaderThread that was created by the TcpConnectionThread. It * will be compared to this thread. If they differ this thread must be a * lock thread that got stuck when the user changed APNs or switched from * WiFi to another data connection type. */ protected static ResponseReaderThread mCurrentThread; /** * Decodes all responses coming in. */ private DecoderThread mDecoder; /** * The main connection thread to call back when an error occured. */ private TcpConnectionThread mConnThread; /** * The input stream to read responses from. */ protected DataInputStream mIs; /** * Indicates that the thread is active and connected. */ protected boolean mIsConnectionRunning; /** * Represents the thread that reads responses. */ protected Thread mThread; /** * The socket to read from. */ private Socket mSocket; /** * * Constructs a new response reader used for reading bytes from a socket * connection. * * @param connThread The connection thread that manages this and the heartbeat sender thread. * It is called back whenever an error occured reading from the socket input stream. * @param decoder Used to decode all incoming responses. * */ public ResponseReaderThread(TcpConnectionThread connThread, DecoderThread decoder, Socket socket) { mConnThread = connThread; mDecoder = decoder; mSocket = socket; if (null != mSocket) { try { mSocket.setSoTimeout(Settings.TCP_SOCKET_READ_TIMEOUT); } catch (SocketException se) { HttpConnectionThread.logE("ResponseReaderThread()", "Could not set socket to!", se); } } } /** * Starts the connection by setting the connection flag and spawning a new * thread in which responses can be read without interfering with the rest * of the client. */ public void startConnection() { HttpConnectionThread.logI("RpgTcpResponseReader.startConnection()", "STARTING Response Reader!"); mIsConnectionRunning = true; mThread = new Thread(this, "RpgTcpResponseReader"); mThread.start(); } /** * Stops the connection by closing the input stream and setting it to null. * Attempts to stop the thread and sets it to null. */ public synchronized void stopConnection() { HttpConnectionThread.logD("ResponseReaderThread.stopConnection()", "Stopping connection..."); mIsConnectionRunning = false; if (null != mSocket) { try { mSocket.shutdownInput(); } catch (IOException ioe) { HttpConnectionThread.logE("RpgTcpHeartbeatSender.stopConnection()", "Could not shutdown InputStream", ioe); } finally { mSocket = null; } } if (null != mIs) { try { mIs.close(); } catch (IOException ioe) { HttpConnectionThread.logE("RpgTcpResponseReader.stopConnection()", "Could not close InputStream", ioe); } finally { mIs = null; } } } /** * Sets the input stream that will be used to read responses from. * * @param inputStream The input stream to read from. Should not be null as * this would trigger a reconnect of the whole socket and cause * all transport-threads to be restarted. */ public void setInputStream(BufferedInputStream inputStream) { HttpConnectionThread.logI("RpgTcpResponseReader.setInputStream()", "Setting new IS: " + ((null != inputStream) ? "not null" : "null")); if (null != inputStream) { mIs = new DataInputStream(inputStream); } else { mIs = null; } } /** * Overrides the Thread.run() method and continuously calls the * readResponses() method which blocks and reads responses or throws an * exception that needs to be handled by reconnecting the sockets. */ public void run() { while (mIsConnectionRunning) { HttpConnectionThread.logD("ResponseReaderThread.run()", "Checking for duplicate threads."); checkForDuplicateThreads(); HttpConnectionThread.logD("ResponseReaderThread.run()", "Reading next response..."); try { byte[] response = readNextResponse(); if ((null != response) && (response.length >= RpgHeader.HEADER_LENGTH)) { mDecoder.handleResponse(response); } } catch (Throwable t) { HttpConnectionThread.logE("RpgTcpResponseReader.run()", "Could not read Response. Unknown: ", t); if ((null != mConnThread) && (mIsConnectionRunning)) { mIsConnectionRunning = false; mConnThread.notifyOfNetworkProblems(); } } if (mIsConnectionRunning) { try { Thread.sleep(THREAD_SLEEP_TIME); } catch (InterruptedException ie) { } } } } /** * <p> * Attempts to read all the bytes from the DataInputStream and writes them * to a byte array where they are processed for further use. * </p> * <p> * As this method uses InputStream.read() it blocks the execution until a * byte has been read, the socket was closed (at which point an IOException * will be thrown), or the end of the file has been reached (resulting in a * EOFException being thrown). * </p> * * @throws IOException Thrown if something went wrong trying to read or * write a byte from or to a stream. * @throws EOFException Thrown if the end of the stream has been reached * unexpectedly. * @return A byte-array if the response was read successfully or null if the * response could not be read. */ private byte[] readNextResponse() throws IOException, EOFException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); int offset = 0; for (int i = 0; i < 2; i++) { // read delimiter, this method blocks int tag = -1; try { tag = mIs.read(); } catch (SocketTimeoutException ste) { HttpConnectionThread.logW("ResponseReaderThread.readNextResponse()", "Socket timed out reading!"); checkForDuplicateThreads(); return null; } if (tag != RpgHeader.DELIMITER_BYTE) { if (tag == -1) { // we reached EOF. This is a network issue throw new EOFException(); } HttpConnectionThread.logI("RpgTcpResponseReader.readResponses()", "Returning... Tag is " + tag + " (" + (char)tag + ")"); return null; } } final byte msgType = mIs.readByte(); final int reqId = mIs.readInt(); final int other = mIs.readInt(); final int payloadSize = mIs.readInt(); final byte compression = mIs.readByte(); dos.writeByte(RpgHeader.DELIMITER_BYTE); dos.writeByte(RpgHeader.DELIMITER_BYTE); dos.writeByte(msgType); dos.writeInt(reqId); dos.writeInt(other); dos.writeInt(payloadSize); dos.writeByte(compression); byte[] payload = new byte[payloadSize]; // read the payload while (offset < payloadSize) { offset += mIs.read(payload, offset, payloadSize - offset); } dos.write(payload); dos.flush(); dos.close(); final byte[] response = baos.toByteArray(); if (Settings.sEnableProtocolTrace) { if (reqId != 0) { // regular response HttpConnectionThread.logI("ResponseReader.readResponses()", "\n" + " < Response for ID " + reqId + " with payload-length " + payloadSize + " received <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + HessianUtils.getInHessian(new ByteArrayInputStream(response), false) + "\n "); } else { // push response HttpConnectionThread.logI("ResponseReader.readResponses()", "\n" + " < Push response " + " with payload-length " + payloadSize + " received <x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x" + HessianUtils.getInHessian(new ByteArrayInputStream(response), false) + "\n "); } } // log file containing response to SD card if (Settings.sEnableSuperExpensiveResponseFileLogging) { LogUtils.logE("XXXXXXYYYXXXXXX Do not Remove this!"); LogUtils.logToFile(response, "people_" + reqId + "_" + System.currentTimeMillis() + "_resp_" + ((int)msgType) + ((compression == 1) ? ".gzip_w_rpg_header" : ".txt")); } // end log file containing response to SD card return response; } /** * Checks whether the current thread is not the same as the thread that * should be running. If it is not the connection and the thread is stopped. */ private void checkForDuplicateThreads() { if (!this.equals(mCurrentThread)) { // The current thread created by the TcpConnectionThread is not // equal to this thread. // This thread must be old (locked due to the user changing his // network settings. HttpConnectionThread.logE("ResponseReaderThread.run()", "This thread is a " + "locked thread caused by the connection settings being switched.", null); stopConnection(); // exit this thread } } }