// 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);
}
}
}