How to Implement Polling for a model
Contents |
Overview
Polling is the process where the computer or controlling device waits for an external device to check for its readiness or state, often with low-level hardware. For example, when a printer is connected via a parallel port, the computer waits until the printer has received the next character. These processes can be as minute as only reading one bit. Τhis is sometimes used synonymously with busy-wait polling. In this situation, when an I/O operation is required, the computer does nothing other than check the status of the I/O device until it is ready, at which point the device is accessed. In other words, the computer waits until the device is ready. Polling also refers to the situation where a device is repeatedly checked for readiness, and if it is not, the computer returns to a different task. Although not as wasteful of CPU cycles as busy waiting, this is generally not as efficient as the alternative to polling, interrupt-driven I/O.
In our case, we will ask for the status of each Mobile Application models(only defined as polling) messages to wait or not to continue with next task depending on the information received by the polling process.
Model definition
First of all, we need to define in our model some attributes for polling usage. See these 3 attributes to define:
- pollingClassName: The name of the Java class which implements the Polling logic for the messages of the model.
- maxAttempts: The maximum number of requests to know the status of the models of this model.
- millisecondsBetweenPollingRequests: Time in milliseconds between attempts of polling requests.
Here you can see an example of a model definition:
this.get('dataSyncModels').push({ name: 'SyncTask', model: OB.Model.SyncTask, modelFunc: 'OB.Model.SyncTask', className: 'org.openbravo.warehouse.advancedwarehouseoperations.mobile.sync.TaskLoader', ... pollingClassName: 'org.openbravo.warehouse.advancedwarehouseoperations.mobile.sync.AWOTasksPollingHandler', maxAttempts: 10, millisecondsBetweenPollingRequests: 1000, ... criteria: { hasBeenProcessed: 'Y' }, getIdentifier: function (model) { if (model && model.id) { return model.id; } else { return OB.I18N.getLabel('OBAWO_NoValidIdentifierForTask'); } } });
Polling implementation Java class
We need to implement the polling execution for the mode. Each model has different polling requirements so we cannot explain exact steps to do the polling but we will explain here few tips to do a good use of the polling infrastructure.
- Polling Java class must extend MobileCoreAbstractPollingHandler
- Must override getFormId(The id of the form configured in the backoffice for the module of the model) method
- Main method to do the polling is processPollingRequest(Override it): This method receives the information to do the polling, will do the polling and will response with the result/status of the messages checked in the polling process.
- Received json will contain messages from the corresponding model with numberOfAttempts, maxAttempts and pauseBetweenRetry values
- Response must have same messages with the status of each message and info like error message or specific relevant info of each model.
See below an example of implementation for task polling in AWO Mobile Application:
public class AWOTasksPollingHandler extends MobileCoreAbstractPollingHandler { public static final Logger log = Logger.getLogger(AWOTasksPollingHandler.class); @Override protected String getFormId() { return OBAWO_Constants.FORM_ID; } @Override public JSONObject processPollingRequest(JSONObject jsonIn) { JSONObject objToReturnWrapper = new JSONObject(); JSONObject objToReturn = new JSONObject(); JSONArray messages; List<String> ids = new ArrayList<String>(); List<String> taskIds = new ArrayList<String>(); try { OBContext.setAdminMode(true); try { messages = jsonIn.getJSONArray("messages"); for (int i = 0; i < messages.length(); i++) { ids.add(((JSONObject) messages.get(i)).getString("messageId")); JSONArray elements = ((JSONObject) messages.get(i)).getJSONArray("elements"); for (int j = 0; j < elements.length(); j++) { if (((JSONObject) elements.get(j)).has("tasks")) { // Grouped task JSONArray tasks = ((JSONObject) elements.get(j)).getJSONArray("tasks"); for (int k = 0; k < elements.length(); k++) { taskIds.add(((JSONObject) tasks.get(k)).getString("id")); } } else { // Simple task taskIds.add(((JSONObject) elements.get(j)).getString("id")); } } } String hqlImportEntries = "select cie.id, cie.importStatus " + "from C_IMPORT_ENTRY as cie where cie.id in (:ids) "; OBDal.getInstance().getSession().createQuery(hqlImportEntries); Query importEntries = OBDal.getInstance().getSession().createQuery(hqlImportEntries); importEntries.setParameterList("ids", ids); for (Object obj : importEntries.list()) { Object[] ie = (Object[]) obj; for (int i = 0; i < messages.length(); i++) { if (((JSONObject) messages.get(i)).getString("messageId").equals(ie[0])) { ((JSONObject) messages.get(i)).put("importStatus", ie[1]); ids.remove(ie[0]); break; } } } ... ... ... objToReturn.put("messages", messages); } catch (JSONException e1) { throw new OBException("Malformed Polling Request.", true); } try { objToReturnWrapper.put("status", 0); objToReturnWrapper.put("data", objToReturn); } catch (JSONException e) { throw new OBException( "Unknow error happened preparing response for polling request for message " + StringUtils.join(ids, ","), true); } } finally { OBContext.restorePreviousMode(); } return objToReturnWrapper; } }
Polling implementation client side
There are two triggers sent by polling process: pollingStarted and pollingFinished. You must implement listeners by each model to get the information or status of the messages of the polling. Here you can find the information structure for both triggers:
- PollingStarted trigger information:
- planedToSendPollingRequest: Messages created in runSyncProcess together with messages pending to synchronize previously created.
- notStartedPollingRequest: Queue of messages which are in the polling process that has just started.
- ongoingPollingRequest: List of messages which are being processed in the backend by PollingHandler and we did not get a response of their status yet.
- PollingFinished trigger information:
- status: "success" | "running" | "error". The status can be success or error but we can start a new polling while there is already a polling being processed an get a running status.
- message: A message to show if we get an error or if we need to show a message information.
- successPollingRequest: List of messages which we could confirm their status
- errorPollingRequest: List of messages which we couldn't confirm their status
- notStartedPollingRequest: Queue of messages which are in the polling process that has just started.
- ongoingPollingRequest: List of messages which are being processed in the backend by PollingHandler and we did not get a response of their status yet.
Here you can find an example of implementation:
var callbackPollingStarted; callbackPollingStarted = function (response) { OB.MobileApp.model.off('pollingStarted', callbackPollingStarted); if (successCallback) { successCallback(); } }; //Start listening to pollingStarted if (OB.MobileApp.model.get('connectedToERP')) { OB.MobileApp.model.on('pollingStarted', callbackPollingStarted); } else { if (errorCallback) { errorCallback({ exception: { message: ' Mobile Application is Offline' } }); } return; }
var errorMessage = OB.I18N.getLabel('OBAWO_PollingOffline'); var callbackPollingFinished; var callbackOffline; callbackPollingFinished = function (response) { if (response.status === 'running') { OB.info('[PollRequest] Polling process is running, wait until Polling execution finish'); return; } OB.MobileApp.model.off('pollingFinished', callbackPollingFinished); OB.MobileApp.model.off('change:connectedToERP', callbackOffline); //When we are offline Polling cannot be completed if (!OB.MobileApp.model.get('connectedToERP')) { OB.error(errorMessage); if (errorCallback) { errorCallback({ exception: { message: errorMessage } }); } return; } else if (response.status === 'error') { OB.UTIL.showError(OB.I18N.getLabel('OBAWO_PollingError')); if (errorCallback) { errorCallback({ exception: { message: OB.I18N.getLabel('OBAWO_PollingError') } }); } return; } else if (response.status === 'success') { if (response.errorPollingRequest.length > 0) { OB.error('[PollRequest] Polling process confirmed next messages as error: ' + _.map(response.errorPollingRequest.models, function (mdl) { return mdl.get('messageId'); }).join(',')); } if (_.any(response.successPollingRequest.models, function (mdl) { return mdl.get('hasoBAWOErrors'); })) { //Check if there are messages in Errors while Importing window and show the message error var content = [{ content: OB.I18N.getLabel('OBAWO_CheckErrorWindow'), style: 'padding: 10px; text-align: left;' }]; _.each(response.successPollingRequest.models, function (mdl) { _.each(mdl.get('elements'), function (elm) { if (elm.tasks) { //Grouped task _.each(elm.tasks, function (tsk) { if (tsk.oBAWOError) { content.push({ content: OB.I18N.getLabel('OBMOBC_Character')[1] + ' ' + tsk.oBAWOError.substring(0, 150) + '...', style: 'padding-left: 30px; text-align: left; font-size:16px' }); } }); } else if (elm.oBAWOError) { //Simple task with error content.push({ content: OB.I18N.getLabel('OBMOBC_Character')[1] + ' ' + elm.oBAWOError.substring(0, 150) + '...', style: 'padding-left: 30px; text-align: left; font-size:16px' }); } }); }); OB.UTIL.showConfirmation.display(OB.I18N.getLabel('OBAWO_TasksErrorHeader'), content, [{ isConfirmButton: true, label: OB.I18N.getLabel('OBMOBC_LblOk'), action: function () { if (successCallback) { successCallback(); } return true; } }], { autoDismiss: false, onHideFunction: function () { if (successCallback) { successCallback(); } return true; } }); } else { if (successCallback) { successCallback(); } } } }; callbackOffline = function (response) { //When we are offline Polling cannot be completed if (!OB.MobileApp.model.get('connectedToERP')) { OB.MobileApp.model.off('pollingFinished', callbackPollingFinished); OB.MobileApp.model.off('change:connectedToERP', callbackOffline); OB.error(errorMessage); if (errorCallback) { errorCallback({ exception: { message: errorMessage } }); } return; } }; //Start listening to pollingFinished if (OB.MobileApp.model.get('connectedToERP')) { OB.MobileApp.model.on('pollingFinished', callbackPollingFinished); OB.MobileApp.model.on('change:connectedToERP', callbackOffline); } else { OB.error(errorMessage); if (noPollingCallback) { noPollingCallback(); } return; }