// Copyright 2015 The Project Buendia Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at: http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software distrib- // uted under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES // OR CONDITIONS OF ANY KIND, either express or implied. See the License for // specific language governing permissions and limitations under the License. package org.projectbuendia.client.models.tasks; import android.content.ContentResolver; import android.content.ContentValues; import android.os.AsyncTask; import com.android.volley.VolleyError; import com.android.volley.toolbox.RequestFuture; import org.projectbuendia.client.events.CrudEventBus; import org.projectbuendia.client.events.data.EncounterAddFailedEvent; import org.projectbuendia.client.events.data.ItemCreatedEvent; import org.projectbuendia.client.events.data.ItemFetchFailedEvent; import org.projectbuendia.client.events.data.ItemFetchedEvent; import org.projectbuendia.client.filter.db.encounter.EncounterUuidFilter; import org.projectbuendia.client.models.Encounter; import org.projectbuendia.client.models.Patient; import org.projectbuendia.client.models.LoaderSet; import org.projectbuendia.client.net.Server; import org.projectbuendia.client.json.JsonEncounter; import org.projectbuendia.client.providers.Contracts.Observations; import org.projectbuendia.client.utils.Logger; import java.util.concurrent.ExecutionException; /** * An {@link AsyncTask} that adds a patient encounter to the server. * <p/> * <p>If the operation succeeds, a {@link ItemCreatedEvent} is posted on the given * {@link CrudEventBus} with the added encounter. If the operation fails, a * {@link EncounterAddFailedEvent} is posted instead. */ public class AddEncounterTask extends AsyncTask<Void, Void, EncounterAddFailedEvent> { // TODO: Factor out common code between this class and AddPatientTask. private static final Logger LOG = Logger.create(); private static final String[] ENCOUNTER_PROJECTION = new String[] { Observations.CONCEPT_UUID, Observations.ENCOUNTER_MILLIS, Observations.ENCOUNTER_UUID, Observations.PATIENT_UUID, Observations.VALUE }; private final TaskFactory mTaskFactory; private final LoaderSet mLoaderSet; private final Server mServer; private final ContentResolver mContentResolver; private final Patient mPatient; private final Encounter mEncounter; private final CrudEventBus mBus; private String mUuid; /** Creates a new {@link AddEncounterTask}. */ public AddEncounterTask( TaskFactory taskFactory, LoaderSet loaderSet, Server server, ContentResolver contentResolver, Patient patient, Encounter encounter, CrudEventBus bus ) { mTaskFactory = taskFactory; mLoaderSet = loaderSet; mServer = server; mContentResolver = contentResolver; mPatient = patient; mEncounter = encounter; mBus = bus; } @Override protected EncounterAddFailedEvent doInBackground(Void... params) { RequestFuture<JsonEncounter> future = RequestFuture.newFuture(); mServer.addEncounter(mPatient, mEncounter, future, future); JsonEncounter jsonEncounter; try { jsonEncounter = future.get(); } catch (InterruptedException e) { return new EncounterAddFailedEvent(EncounterAddFailedEvent.Reason.INTERRUPTED, e); } catch (ExecutionException e) { LOG.e(e, "Server error while adding encounter"); EncounterAddFailedEvent.Reason reason = EncounterAddFailedEvent.Reason.UNKNOWN_SERVER_ERROR; if (e.getCause() != null) { String errorMessage = e.getCause().getMessage(); if (errorMessage.contains("failed to validate")) { reason = EncounterAddFailedEvent.Reason.FAILED_TO_VALIDATE; } else if (errorMessage.contains("Privileges required")) { reason = EncounterAddFailedEvent.Reason.FAILED_TO_AUTHENTICATE; } } LOG.e("Error response: %s", ((VolleyError) e.getCause()).networkResponse); return new EncounterAddFailedEvent(reason, (VolleyError) e.getCause()); } if (jsonEncounter.uuid == null) { LOG.e( "Although the server reported an encounter successfully added, it did not " + "return a UUID for that encounter. This indicates a server error."); return new EncounterAddFailedEvent( EncounterAddFailedEvent.Reason.FAILED_TO_SAVE_ON_SERVER, null /*exception*/); } Encounter encounter = Encounter.fromJson(mPatient.uuid, jsonEncounter); ContentValues[] values = encounter.toContentValuesArray(); if (values.length > 0) { int inserted = mContentResolver.bulkInsert(Observations.CONTENT_URI, values); if (inserted != values.length) { LOG.w("Inserted %d observations for encounter. Expected: %d", inserted, encounter.observations.length); return new EncounterAddFailedEvent( EncounterAddFailedEvent.Reason.INVALID_NUMBER_OF_OBSERVATIONS_SAVED, null /*exception*/); } } else { LOG.w("Encounter was sent to the server but contained no observations."); } mUuid = jsonEncounter.uuid; return null; } @Override protected void onPostExecute(EncounterAddFailedEvent event) { // If an error occurred, post the error event. if (event != null) { mBus.post(event); return; } // If the UUID was not set, a programming error occurred. Log and post an error event. if (mUuid == null) { LOG.e( "Although an encounter add ostensibly succeeded, no UUID was set for the newly-" + "added encounter. This indicates a programming error."); mBus.post(new EncounterAddFailedEvent( EncounterAddFailedEvent.Reason.UNKNOWN, null /*exception*/)); return; } // Otherwise, start a fetch task to fetch the encounter from the database. mBus.register(new CreationEventSubscriber()); FetchItemTask<Encounter> task = mTaskFactory.newFetchItemTask( Observations.CONTENT_URI, ENCOUNTER_PROJECTION, new EncounterUuidFilter(), mUuid, new Encounter.Loader(mPatient.uuid), mBus); task.execute(); } // After updating an encounter, we fetch the encounter from the database. The result of the // fetch determines if adding a patient was truly successful and propagates a new event to // report success/failure. @SuppressWarnings("unused") // Called by reflection from EventBus. private final class CreationEventSubscriber { public void onEventMainThread(ItemFetchedEvent<Encounter> event) { mBus.post(new ItemCreatedEvent<>(event.item)); mBus.unregister(this); } public void onEventMainThread(ItemFetchFailedEvent event) { mBus.post(new EncounterAddFailedEvent( EncounterAddFailedEvent.Reason.FAILED_TO_FETCH_SAVED_OBSERVATION, new Exception(event.error))); mBus.unregister(this); } } }