/* * 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.io; import java.util.ArrayList; import java.util.List; import com.vodafone360.people.Settings; import com.vodafone360.people.service.io.ResponseQueue.DecodedResponse; import com.vodafone360.people.service.transport.IQueueListener; import com.vodafone360.people.service.transport.http.HttpConnectionThread; import com.vodafone360.people.service.utils.TimeOutWatcher; import com.vodafone360.people.utils.LogUtils; /** * Holds a queue of outgoing requests. The Requester adds Requests to the queue. * The transport layer gets one or more items from the queue when it is ready to * send more requests to the server. When a Request is added a request id is * generated for this request. Requests are removed from the queue on completion * or if an error requires us to clear any outstanding requests. */ public class RequestQueue { private final static int MILLIS_PER_SECOND = 1000; /** * The queue data, a List-array of Request items. */ private final List<Request> mRequests = new ArrayList<Request>(); /** * A unique ID identifying this request */ private volatile int mCurrentRequestId; /** * Contains a list of listeners that will receive events when items are * added to the queue. */ private final List<IQueueListener> mListeners = new ArrayList<IQueueListener>(); private TimeOutWatcher mTimeOutWatcher; /** * Constructs the request queue */ protected RequestQueue() { // Generate initial request ID based on current timestamp. mCurrentRequestId = (int)(System.currentTimeMillis() / MILLIS_PER_SECOND); mTimeOutWatcher = new TimeOutWatcher(); } /** * Get instance of RequestQueue - we only have a single instance. If the * instance of RequestQueue does not yet exist it is created. * * @return Instance of RequestQueue. */ protected static RequestQueue getInstance() { return RequestQueueHolder.rQueue; } /** * Use Initialization on demand holder pattern */ private static class RequestQueueHolder { private static final RequestQueue rQueue = new RequestQueue(); } /** * Add listener listening for RequestQueue changes. Events are sent when * items are added to the queue (or in the case of batching when the last * item is added to the queue). * * @param listener listener to add */ protected void addQueueListener(IQueueListener listener) { LogUtils.logW("RequestQueue.addQueueListener() listener[" + listener + "]"); synchronized (mListeners) { if (mListeners != null) { mListeners.add(listener); } } } /** * Remove RequestQueue listener * * @param listener listener to remove */ protected void removeQueueListener(IQueueListener listener) { LogUtils.logW("RequestQueue.removeQueueListener() listener[" + listener + "]"); synchronized (mListeners) { if (mListeners != null) { mListeners.remove(listener); } } } /** * Fire RequestQueue state changed message */ protected void fireQueueStateChanged() { synchronized (mListeners) { LogUtils.logW("RequestQueue.notifyOfItemInRequestQueue() listener[" + mListeners + "]"); for (IQueueListener listener : mListeners) { listener.notifyOfItemInRequestQueue(); } } } /** * Add request to queue * * @param req Request to add to queue * @return request id of new request */ protected int addRequestAndNotify(Request req) { synchronized (QueueManager.getInstance().lock) { int ret = addRequest(req); fireQueueStateChanged(); return ret; } } /** * Adds a request to the queue without sending an event to the listeners * * @param req The request to add * @return The unique request ID TODO: What is with the method naming * convention? */ protected int addRequest(Request req) { synchronized (QueueManager.getInstance().lock) { mCurrentRequestId++; req.setRequestId(mCurrentRequestId); mRequests.add(req); // add the request to the watcher thread if (req.getTimeout() > 0 && (!req.isFireAndForget() || // We now use the timeout mechanism for Request.Type.AVAILABILITY of request, // so we should not remove it from the queue otherwise there will be no timeout triggered. (req.mType == Request.Type.AVAILABILITY))) { // TODO: maybe the expiry date should be calculated when the // request is actually sent? req.calculateExpiryDate(); mTimeOutWatcher.addRequest(req); } HttpConnectionThread.logV("RequestQueue.addRequest", "Adding request to queue:\n" + req.toString()); return mCurrentRequestId; } } /** * Adds requests to the queue. * * @param requests The requests to add. * @return The request IDs generated in an integer array or null if the * requests array was null. Returns NULL id requests[] is NULL. */ protected int[] addRequest(Request[] requests) { synchronized (QueueManager.getInstance().lock) { if (null == requests) { return null; } int[] requestIds = new int[requests.length]; for (int i = 0; i < requests.length; i++) { requestIds[i] = addRequest(requests[i]); } return requestIds; } } /* * Get number of items currently in the list of requests * @return number of request items */ private int requestCount() { return mRequests.size(); } /** * Returns all requests from the queue. Regardless if they need to * * @return List of all requests. */ protected List<Request> getAllRequests() { synchronized (QueueManager.getInstance().lock) { return mRequests; } } /** * Returns all requests from the queue needing the API or both to work. * * @return List of all requests needing the API or both (API or RPG) to * function properly. */ protected List<Request> getApiRequests() { synchronized (QueueManager.getInstance().lock) { return this.getRequests(false); } } /** * Returns all requests from the queue needing the RPG or both to work. * * @return List of all requests needing the RPG or both (API or RPG) to * function properly. */ protected List<Request> getRpgRequests() { return this.getRequests(true); } /** * Returns a list of either requests needing user authentication or requests * not needing user authentication depending on the flag passed to this * method. * * @param needsUserAuthentication If true only requests that need to have a * valid user authentication will be returned. Otherwise methods * requiring application authentication will be returned. * @return A list of requests with the need for application authentication * or user authentication. */ private List<Request> getRequests(boolean needsRpgForRequest) { synchronized (QueueManager.getInstance().lock) { List<Request> requests = new ArrayList<Request>(); if (null == mRequests) { return requests; } Request request = null; for (int i = 0; i < mRequests.size(); i++) { request = mRequests.get(i); if ((null == request) || (request.isActive())) { LogUtils.logD("Skipping active or null request in request queue."); continue; } HttpConnectionThread.logD("RequestQueu.getRequests()", "Request Auth Type (USE_API=1, USE_RPG=2, USE_BOTH=3): " + request.getAuthenticationType()); // all api and rpg requests if (request.getAuthenticationType() == Request.USE_BOTH) { requests.add(request); } else if ((!needsRpgForRequest) && (request.getAuthenticationType() == Request.USE_API)) { requests.add(request); } else if ((needsRpgForRequest) && (request.getAuthenticationType() == Request.USE_RPG)) { requests.add(request); // all rpg requests } } return requests; } } /** * Return Request from specified request ID. Only used for unit tests. * * @param requestId Request Id of required request * @return Request with or null if request does not exist */ protected Request getRequest(int requestId) { Request req = null; int reqCount = requestCount(); for (int i = 0; i < reqCount; i++) { Request tmp = mRequests.get(i); if (tmp.getRequestId() == requestId) { req = tmp; break; } } return req; } /** * Removes the request for the given response (request) ID from the queue and searches * the queue for requests older than * Settings.REMOVE_REQUEST_FROM_QUEUE_MILLIS and removes them as well. * * @param responseId The response object id. * @return Returns the removed request, can be null if the request was not found. */ protected Request removeRequest(int responseId) { synchronized (QueueManager.getInstance().lock) { for (int i = 0; i < requestCount(); i++) { Request request = mRequests.get(i); // the request we were looking for if (request.getRequestId() == responseId) { // reassure the engine id is set (important for SystemNotifications) mRequests.remove(i--); // remove the request from the watcher (the request not // necessarily times out before) if (request.getExpiryDate() > 0) { mTimeOutWatcher.removeRequest(request); LogUtils .logV("RequestQueue.removeRequest() Request expired after [" + (System.currentTimeMillis() - request.getAuthTimestamp()) + "ms]"); } else { LogUtils .logV("RequestQueue.removeRequest() Request took [" + (System.currentTimeMillis() - request.getAuthTimestamp()) + "ms]"); } return request; } else if ((System.currentTimeMillis() - request.getCreationTimestamp()) > Settings.REMOVE_REQUEST_FROM_QUEUE_MILLIS) { // request // is older than 15 minutes mRequests.remove(i--); ResponseQueue.getInstance().addToResponseQueue(new DecodedResponse(request.getRequestId(), null, request.mEngineId, DecodedResponse.ResponseType.TIMED_OUT_RESPONSE.ordinal())); // remove the request from the watcher (the request not // necessarily times out before) if (request.getExpiryDate() > 0) { mTimeOutWatcher.removeRequest(request); LogUtils .logV("RequestQueue.removeRequest() Request expired after [" + (System.currentTimeMillis() - request.getAuthTimestamp()) + "ms]"); } else { LogUtils .logV("RequestQueue.removeRequest() Request took [" + (System.currentTimeMillis() - request.getAuthTimestamp()) + "ms]"); } } } return null; } } /** * Return the current (i.e. most recently generated) request id. * * @return the current request id. */ /* * public synchronized int getCurrentId(){ return mCurrentRequestId; } */ /** * Clear active requests (i.e add dummy response to response queue). * * @param rpgOnly */ protected void clearActiveRequests(boolean rpgOnly) { synchronized (QueueManager.getInstance().lock) { ResponseQueue rQ = ResponseQueue.getInstance(); for (int i = 0; i < mRequests.size(); i++) { Request request = mRequests.get(i); if (request.isActive() && (!rQ.responseExists(request.getRequestId()))) { if (!rpgOnly || (rpgOnly && ((request.getAuthenticationType() == Request.USE_RPG) || (request .getAuthenticationType() == Request.USE_BOTH)))) { LogUtils.logE("RequestQueue.clearActiveRequests() Deleting request " + request.getRequestId()); mRequests.remove(i); // AA: I added the line below // remove the request from the watcher (the request not // necessarily times out before) if (request.getExpiryDate() > 0) { mTimeOutWatcher.removeRequest(request); } i--; rQ.addToResponseQueue(new DecodedResponse(request.getRequestId(), null, request.mEngineId, DecodedResponse.ResponseType.TIMED_OUT_RESPONSE.ordinal())); } } } } } /** * Clears all requests from the queue and puts null responses on the * response queue to tell the engines that they have been cleared. This * should be called from the connection thread as soon as it is stopped. */ protected void clearAllRequests() { synchronized (QueueManager.getInstance().lock) { ResponseQueue responseQueue = ResponseQueue.getInstance(); for (int i = 0; i < mRequests.size(); i++) { Request request = mRequests.get(i); LogUtils.logE("RequestQueue.clearActiveRequests() Deleting request " + request.getRequestId()); mRequests.remove(i--); // remove the request from the watcher (the request not // necessarily times out before) if (request.getExpiryDate() > 0) { mTimeOutWatcher.removeRequest(request); } responseQueue.addToResponseQueue(new DecodedResponse(request.getRequestId(), null, request.mEngineId, DecodedResponse.ResponseType.TIMED_OUT_RESPONSE.ordinal())); } } } /** * Return handle to TimeOutWatcher. * * @return handle to TimeOutWatcher. */ protected TimeOutWatcher getTimeoutWatcher() { return mTimeOutWatcher; } /** * Removes all items that are being watched for timeouts */ protected void clearTheTimeOuts() { if (mTimeOutWatcher != null) { mTimeOutWatcher.kill(); } } /** * Overrides the toString() method of Object and gives detailed infos which * objects are on the queue and whether they are active or not. */ @Override public String toString() { if (null == mRequests) { return ""; } final StringBuffer sb = new StringBuffer("Queue Size: "); sb.append(mRequests.size()); sb.append("; Request method-name [isActive]: "); for (int i = 0; i < mRequests.size(); i++) { Request request = mRequests.get(i); if (null == request) { sb.append("null request"); } else { sb.append(request.getApiMethodName()); sb.append(" ["); sb.append(request.isActive()); sb.append("]"); } if (i < (mRequests.size() - 1)) { sb.append(", "); } } return sb.toString(); } }