/*
* 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.engine;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import android.os.Bundle;
import com.vodafone360.people.datatypes.BaseDataType;
import com.vodafone360.people.datatypes.ServerError;
import com.vodafone360.people.engine.EngineManager.EngineId;
import com.vodafone360.people.service.ServiceStatus;
import com.vodafone360.people.service.ServiceUiRequest;
import com.vodafone360.people.service.agent.UiAgent;
import com.vodafone360.people.service.io.ResponseQueue;
import com.vodafone360.people.utils.LogUtils;
/**
* Base-class for all Engines implemented by the People Client.
*/
public abstract class BaseEngine {
/**
* All engines must set this field to a unique ID
*/
protected EngineId mEngineId = EngineId.UNDEFINED;
/**
* Callback provided by {@link EngineManager}
*/
protected IEngineEventCallback mEventCallback;
/**
* Current UI Request if one is active, otherwise null.
*
* @see ServiceUiRequest
*/
protected ServiceUiRequest mActiveUiRequest;
/**
* Current timeout based on current time in milliseconds if one is pending,
* otherwise null.
*/
protected volatile Long mCurrentTimeout;
/**
* true if a Comms response is waiting in the comms response queue for
* processing, false otherwise.
*/
private volatile boolean mCommsResponseOutstanding = false;
/**
* Set by the {@link #setReqId(int)} function to store the request ID when a
* engine makes a request to the server. All responses received will then be
* filtered by this value automatically inside the
* {@link #processCommsInQueue()} function.
*/
private Integer mActiveRequestId;
/**
* true if a UI request is waiting in the UI request queue for processing,
* false otherwise.
*/
private boolean mUiRequestOutstanding;
/**
* true if the engine is deactivated (for test purposes), false otherwise.
*/
private boolean mDeactivated;
/**
* Class to encapsulate client (UI) request information.
*/
private static class UiQueueItem {
/**
* Request type.
*/
private ServiceUiRequest mRequestId;
/**
* Data associated with the request.
*/
private Object mData;
@Override
public String toString() {
return "UiQueueItem [mData=" + mData + ", mRequestId=" + mRequestId + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((mData == null) ? 0 : mData.hashCode());
result = prime * result + ((mRequestId == null) ? 0 : mRequestId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
UiQueueItem other = (UiQueueItem)obj;
if (mData == null) {
if (other.mData != null)
return false;
} else if (!mData.equals(other.mData))
return false;
if (mRequestId == null) {
if (other.mRequestId != null)
return false;
} else if (!mRequestId.equals(other.mRequestId))
return false;
return true;
}
}
/**
* Used to implement the UI request queue.
*/
private final ConcurrentLinkedQueue<UiQueueItem> mUiQueue = new ConcurrentLinkedQueue<UiQueueItem>();
/**
* Public constructor.
*
* @param eventCallback This interface must be implemented by the engine
* client
*/
public BaseEngine(IEngineEventCallback eventCallback) {
mEventCallback = eventCallback;
mDeactivated = false;
}
/**
* Return the absolute time in milliseconds when the engine needs to run
* (based on System.currentTimeMillis). (Maybe add currentTime in the future
* to use as the current time, to enable JUnit tests to validate timeout
* functionality).
*
* @return -1 never needs to run, 0 needs to run as soon as possible,
* CurrentTime + 60000 to run in 1 minute, etc.
*/
public abstract long getNextRunTime();
/**
* Do some work but anything that takes longer than 1 second must be broken
* up. (Maybe add currentTime as parameter, to enable JUnit tests to
* validate timeout functionality)
*/
public abstract void run();
/**
* This will be called immediately after creation.
*/
public abstract void onCreate();
/**
* This will be called just before the engine is shutdown.
*/
public abstract void onDestroy();
/**
* Helper function for use by the derived engine class to add a UI request
* to the queue. This will be run from the UI thread
* @param request - {@link ServiceUiRequest}.
* @param data - Object containing data related to the request.
*/
public void addUiRequestToQueue(ServiceUiRequest request, Object data) {
addUiRequestToQueue(request, data, false);
}
/**
* Helper function for use by the derived engine class to add a UI request
* to the queue. This will be run from the UI thread
* @param request - {@link ServiceUiRequest}.
* @param data - Object containing data related to the request.
* @param throttle - boolean, pass TRUE if the engine should ignore the incoming ServiceUiRequests
* when the request queue already contains requests of the same type waiting to be processed.
*/
public void addUiRequestToQueue(ServiceUiRequest request, Object data, boolean throttle) {
if (mDeactivated) {
onUiRequestComplete(request, ServiceStatus.ERROR_NOT_IMPLEMENTED, null);
return;
}
UiQueueItem item = new UiQueueItem();
item.mRequestId = request;
item.mData = data;
synchronized (mUiQueue) {
if (!throttle || !mUiQueue.contains(item)) {
mUiQueue.add(item);
LogUtils.logD("Added to the queue, ServiceUiRequest."+request);
}
mUiRequestOutstanding = true;
}
mEventCallback.kickWorkerThread();
}
/**
* Return id for this engine.
*
* @return EngineId identifying concrete engine
*/
public EngineId engineId() {
return mEngineId;
}
/**
* Helper function for use by the derived engine class to fetch the next UI
* request from the queue. Returns null if the queue is empty.
*/
private UiQueueItem fetchNextUiRequest() {
UiQueueItem item = null;
synchronized (mUiQueue) {
item = mUiQueue.poll();
if (mUiQueue.isEmpty()) {
mUiRequestOutstanding = false;
}
}
return item;
}
/**
* Helper function to determine if there is any work outstanding in the UI
* request queue. Returns false if the queue is empty.
*/
protected boolean isUiRequestOutstanding() {
synchronized (mUiQueue) {
// mActiveUiRequest must not be null if its not
// and there is more than one request in a queue
// engine might go into endless loop
if (mActiveUiRequest != null) {
return false;
}
return mUiRequestOutstanding;
}
}
/**
* Helper function which must be called to complete a UI request. Normally
* this will not be called directly by the derived engine implementation.
* Instead the completeUiRequest function should be used.
*
* @param request The request Id to complete
* @param status The ServiceStatus code
* @param data Response data (object type is request specific)
*/
private void onUiRequestComplete(ServiceUiRequest request, ServiceStatus status, Object data) {
mEventCallback.onUiEvent(ServiceUiRequest.UI_REQUEST_COMPLETE, request.ordinal(), status
.ordinal(), data);
}
/**
* The derived engine implementation must call the processCommsInQueue()
* function (normally from within the run() implementation), otherwise this
* will not be called. This function is called for each Comms response that
* arrives on the in queue.
*
* @param resp The comms response
*/
protected abstract void processCommsResponse(ResponseQueue.DecodedResponse resp);
/**
* The derived engine implementation must call the processUiQueue() function
* (normally from within the run() implementation), otherwise this will not
* be called. This function is called for each UI request that arrives on
* the queue. It should start processing the request. If this function takes
* longer than 1 second to complete, it should be broken up. Once a request
* is finished the processUiRequest function must be called.
*
* @param requestId The UI request ID
* @param data Request data (object type is request specific)
*/
protected abstract void processUiRequest(ServiceUiRequest requestId, Object data);
/**
* The derived engine implementation must call the processTimeout() function
* (normally from within the run() implementation), otherwise this will not
* be called. This function will be called when a timeout occurs (started by
* setTimeout and cancelled by clearTimeout). If this function takes longer
* than 1 second to complete it should be broken up.
*/
protected abstract void onTimeoutEvent();
/**
* Called by the EngineManager when a comms response is received. Will set
* the response outstanding flag and kick the worker thread.
*/
public void onCommsInMessage() {
mCommsResponseOutstanding = true;
mEventCallback.kickWorkerThread();
}
/**
* Should be called by the getNextRunTime() and run() functions to check if
* there are any comms responses waiting to be processed.
*
* @return true if there are 1 or more responses to process.
*/
protected boolean isCommsResponseOutstanding() {
return mCommsResponseOutstanding;
}
/**
* Should be called by the run() function to process the comms in queue.
* Calling this function will result in the processCommsResponse function
* being called once. The derived engine implementation should do its
* processing of each response in that function. If the engine set the
* request ID using the setReqId function then messages which don't match
* will be taken off the queue and deleted.
*
* @return true if a response was taken from the queue and processed.
*/
protected boolean processCommsInQueue() {
final ResponseQueue queue = ResponseQueue.getInstance();
if (queue != null) {
final ResponseQueue.DecodedResponse resp = queue.getNextResponse(mEngineId);
if (resp == null) {
mCommsResponseOutstanding = false;
return false;
}
boolean processResponse = false;
if (resp.mReqId == null || mActiveRequestId == null) {
processResponse = true;
} else if (mActiveRequestId.equals(resp.mReqId)) {
mActiveRequestId = null;
processResponse = true;
}
if (processResponse) {
processCommsResponse(resp);
}
return processResponse;
} else {
throw new RuntimeException(
"BaseEngine.processCommsInQueue - ResponseQueue cannot be null");
}
}
/**
* A helper function for the derived engine implementation to use. It checks
* the returned comms response data against the expected type and handles
* all common error cases.
*
* @param requiredResp The expected type
* @param data The data received from the comms response
* @return SUCCESS if the first element in the list is of the expected type,
* ERROR_COMMS if the first element in the list is an unexpected
* type, ERROR_COMMS_BAD_RESPONSE if data is not valid for specyfied
* type or null otherwise if the data is of type ZError, a suitable
* error code.
*/
public static ServiceStatus getResponseStatus(int requiredResponseType,
List<BaseDataType> data) {
ServiceStatus errorStatus = ServiceStatus.ERROR_COMMS;
if (data != null) {
if (data.size() == 0 || data.get(0).getType() == requiredResponseType) {
if (requiredResponseType == BaseDataType.CONTACT_CHANGES_DATA_TYPE &&
data.size() == 0) {
errorStatus = ServiceStatus.ERROR_COMMS_BAD_RESPONSE;
} else {
errorStatus = ServiceStatus.SUCCESS;
}
} else if (data.get(0).getType() == BaseDataType.SERVER_ERROR_DATA_TYPE) {
final ServerError error = (ServerError)data.get(0);
LogUtils.logE("Server error: " + error);
errorStatus = error.toServiceStatus();
} else {
LogUtils.logD(
"BaseEngine.genericHandleResponse: Unexpected type [" + requiredResponseType
+ "] but received [" + data.get(0).getType() + "]");
}
} else {
errorStatus = ServiceStatus.ERROR_COMMS_BAD_RESPONSE;
}
return errorStatus;
}
/**
* Should be called by the run() function to process the UI request queue.
* Calling this function will result in the processUiRequest function being
* called once and this will be set to the active request. The derived
* engine implementation should do its processing of each request in that
* function. Note the engine must not process any more requests until the
* current one has been completed.
*
* @return true if a response was taken from the queue and processed.
*/
protected boolean processUiQueue() {
if (mActiveUiRequest != null) {
return false;
}
final UiQueueItem uiItem = fetchNextUiRequest();
if (uiItem != null) {
mActiveUiRequest = uiItem.mRequestId;
processUiRequest(uiItem.mRequestId, uiItem.mData);
return true;
}
return false;
}
/**
* A helper function that can be called by the derived engine implementation
* to complete the current UI request.
*
* @param status The result of the request
*/
protected void completeUiRequest(ServiceStatus status) {
completeUiRequest(status, null);
}
/**
* This function must be implemented in the derived engine implementation.
* It can do any post-request complete cleanup.
*/
protected abstract void onRequestComplete();
/**
* A helper function that can be called by the derived engine implementation
* to complete the current UI request.
*
* @param status The result of the request
* @param data Response data (object type is request specific)
*/
protected void completeUiRequest(ServiceStatus status, Object data) {
onRequestComplete();
if (mActiveUiRequest != null) {
onUiRequestComplete(mActiveUiRequest, status, data);
mActiveUiRequest = null;
}
}
/**
* This method was added to provide a the engine a way to notify UI of
* unsolicited incoming messages/errors
*
* @param request - the result of the request
* @param status - SUCCESS or ERROR
* @param data Response data (object type is request specific)
*/
/*
* protected void completeUnsolicitedUiRequest(ServiceUiRequest request,
* ServiceStatus status, Object data) { onRequestComplete();
* onUiRequestComplete(request, status, data); mActiveUiRequest = null; //
* TODO: check if we need to set it null }
*/
/**
* Helper function that can be called by the derived engine implementation
* to start an asynchronous timer. This will only work when: 1)
* getCurrentTimeout() is called inside the getNextRunTime() function. 2)
* The processTimeout() is called in the run() function 3) The
* onTimeoutEvent() is implemented to handle the timeout event.
*
* @param timeoutVal The timeout value (in milliseconds)
*/
protected void setTimeout(long timeoutVal) {
mCurrentTimeout = System.currentTimeMillis() + timeoutVal;
}
/**
* Cancels the current timer (has no effect if the timer was not active).
*/
protected void clearTimeout() {
mCurrentTimeout = null;
}
/**
* The result of this function must be returned by getNextRunTime() instead
* of -1 for the timer to work.
*
* @return The required next run time (in milliseconds) or -1 if no timer is
* active
*/
protected long getCurrentTimeout() {
if (mCurrentTimeout != null) {
return mCurrentTimeout;
}
return -1;
}
/**
* This function must be called by run() in the derived engine
* implementation for the timer to work.
*
* @return true if the timeout was processed
*/
protected boolean processTimeout() {
if (mCurrentTimeout != null) {
long currentTimeMs = System.currentTimeMillis();
if (currentTimeMs >= mCurrentTimeout) {
mCurrentTimeout = null;
onTimeoutEvent();
return true;
}
}
return false;
}
/**
* Called by the framework to deactivate the engine. This allows engines to
* be deactivated by modifying the Settings (so features can be disabled in
* the build).
*/
protected void deactivateEngine() {
mDeactivated = true;
}
/**
* Called by the framework to determine if the engine should run.
*/
public boolean isDeactivated() {
return mDeactivated;
}
/**
* Called by engines when an API call is made to ensure that the response
* processed matches the request. If this function is not called the
* framework will send all responses to the engine.
*/
protected boolean setReqId(int reqId) {
if (reqId == -1) {
return false;
}
mActiveRequestId = reqId;
return true;
}
/**
* Called by engines in the UI thread to cancel all UI requests. The engine
* should ensure that the active UI request is completed (if necessary)
* before calling this function.
*/
protected void emptyUiRequestQueue() {
synchronized (mUiQueue) {
mUiQueue.clear();
mUiRequestOutstanding = false;
mActiveUiRequest = null;
}
}
/**
* Engines can override this function to do any special handling when a reset is needed.
* Note: if overriden, the engine shall call the super implementation when the reset is done.
*/
public void onReset() {
emptyUiRequestQueue();
clearTimeout();
}
/**
* This method fires states change of an engine to UI (between "busy" and
* IDLE). This method is normally called after the new state has been
* changed and published to ApplicationCache. The UI should refer to
* ApplicationCache when processing this new ServiceRequest
*
* @param request ServiceUIRequest UPDATING_UI or UPDATING_UI_FINISHED
*/
public void fireNewState(ServiceUiRequest request, Bundle bundle) {
UiAgent uiAgent = mEventCallback.getUiAgent();
if (uiAgent != null && uiAgent.isSubscribed()) {
uiAgent.sendUnsolicitedUiEvent(request, bundle);
}
}
}