content: /*
   * 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();
      }
  }
  